Skip to content
Snippets Groups Projects
Commit b4fddad7 authored by Todd Gamblin's avatar Todd Gamblin
Browse files

Fix for SPACK-10: Spack now forks before install()

- this allows each install to have full control over its environment,
  and over spack.

- build process can do whatever it wants and doesn't affect main Spack
  process.
parent 554ae9b3
No related branches found
No related tags found
No related merge requests found
...@@ -86,8 +86,11 @@ def remove_path_for_spec(self, spec): ...@@ -86,8 +86,11 @@ def remove_path_for_spec(self, spec):
shutil.rmtree(path, True) shutil.rmtree(path, True)
path = os.path.dirname(path) path = os.path.dirname(path)
while not os.listdir(path) and path != self.root: while path != self.root:
os.rmdir(path) if os.path.isdir(path):
if os.listdir(path):
return
os.rmdir(path)
path = os.path.dirname(path) path = os.path.dirname(path)
......
...@@ -33,9 +33,9 @@ ...@@ -33,9 +33,9 @@
rundown on spack and how it differs from homebrew, look at the rundown on spack and how it differs from homebrew, look at the
README. README.
""" """
import inspect
import os import os
import re import re
import inspect
import subprocess import subprocess
import platform as py_platform import platform as py_platform
import multiprocessing import multiprocessing
...@@ -387,8 +387,9 @@ def default_version(self): ...@@ -387,8 +387,9 @@ def default_version(self):
try: try:
return url.parse_version(self.__class__.url) return url.parse_version(self.__class__.url)
except UndetectableVersionError: except UndetectableVersionError:
tty.die("Couldn't extract a default version from %s. You " + raise PackageError(
"must specify it explicitly in the package." % self.url) "Couldn't extract a default version from %s." % self.url,
" You must specify it explicitly in the package file.")
@property @property
...@@ -490,7 +491,7 @@ def virtual_dependencies(self, visited=None): ...@@ -490,7 +491,7 @@ def virtual_dependencies(self, visited=None):
@property @property
def installed(self): def installed(self):
return os.path.exists(self.prefix) return os.path.isdir(self.prefix)
@property @property
...@@ -544,10 +545,11 @@ def do_fetch(self): ...@@ -544,10 +545,11 @@ def do_fetch(self):
raise ValueError("Can only fetch concrete packages.") raise ValueError("Can only fetch concrete packages.")
if spack.do_checksum and not self.version in self.versions: if spack.do_checksum and not self.version in self.versions:
tty.die("Cannot fetch %s@%s safely; there is no checksum on file for this " raise ChecksumError(
"version." % (self.name, self.version), "Cannot fetch %s safely; there is no checksum on file for version %s."
"Add a checksum to the package file, or use --no-checksum to " % self.version,
"skip this check.") "Add a checksum to the package file, or use --no-checksum to "
"skip this check.")
self.stage.fetch() self.stage.fetch()
...@@ -557,8 +559,9 @@ def do_fetch(self): ...@@ -557,8 +559,9 @@ def do_fetch(self):
if checker.check(self.stage.archive_file): if checker.check(self.stage.archive_file):
tty.msg("Checksum passed for %s" % self.name) tty.msg("Checksum passed for %s" % self.name)
else: else:
tty.die("%s checksum failed for %s. Expected %s but got %s." raise ChecksumError(
% (checker.hash_name, self.name, digest, checker.sum)) "%s checksum failed for %s." % checker.hash_name,
"Expected %s but got %s." % (self.name, digest, checker.sum))
def do_stage(self): def do_stage(self):
...@@ -640,32 +643,56 @@ def do_install(self): ...@@ -640,32 +643,56 @@ def do_install(self):
self.do_patch() self.do_patch()
# create the install directory (allow the layout to handle this in # Fork a child process to do the build. This allows each
# case it needs to add extra files) # package authors to have full control over their environment,
spack.install_layout.make_path_for_spec(self.spec) # etc. without offecting other builds that might be executed
# in the same spack call.
tty.msg("Building %s." % self.name)
try: try:
pid = os.fork()
except OSError, e:
raise InstallError("Unable to fork build process: %s" % e)
if pid == 0:
tty.msg("Building %s." % self.name)
# create the install directory (allow the layout to handle
# this in case it needs to add extra files)
spack.install_layout.make_path_for_spec(self.spec)
# Set up process's build environment before running install.
build_env.set_build_environment_variables(self) build_env.set_build_environment_variables(self)
build_env.set_module_variables_for_package(self) build_env.set_module_variables_for_package(self)
self.install(self.spec, self.prefix) try:
if not os.path.isdir(self.prefix): # Subclasses implement install() to do the build &
tty.die("Install failed for %s. No install dir created." % self.name) # install work.
self.install(self.spec, self.prefix)
tty.msg("Successfully installed %s" % self.name)
print_pkg(self.prefix)
except Exception, e: if not os.listdir(self.prefix):
self.remove_prefix() raise InstallError(
raise "Install failed for %s. Nothing was installed!"
% self.name)
finally: # On successful install, remove the stage.
# Once the install is done, destroy the stage where we built it, # Leave if if there is an error
# unless the user wants it kept around.
if not self.dirty:
self.stage.destroy() self.stage.destroy()
tty.msg("Successfully installed %s" % self.name)
print_pkg(self.prefix)
sys.exit(0)
except Exception, e:
self.remove_prefix()
raise
# Parent process just waits for the child to complete. If the
# child exited badly, assume it already printed an appropriate
# message. Just make the parent exit with an error code.
pid, returncode = os.waitpid(pid, 0)
if returncode != 0:
sys.exit(1)
def do_install_dependencies(self): def do_install_dependencies(self):
# Pass along paths of dependencies here # Pass along paths of dependencies here
...@@ -684,16 +711,16 @@ def module(self): ...@@ -684,16 +711,16 @@ def module(self):
def install(self, spec, prefix): def install(self, spec, prefix):
"""Package implementations override this with their own build configuration.""" """Package implementations override this with their own build configuration."""
tty.die("Packages must provide an install method!") raise InstallError("Package %s provides no install method!" % self.name)
def do_uninstall(self): def do_uninstall(self):
if not self.installed: if not self.installed:
tty.die(self.name + " is not installed.") raise InstallError(self.name + " is not installed.")
if not self.ignore_dependencies: if not self.ignore_dependencies:
deps = self.installed_dependents deps = self.installed_dependents
if deps: tty.die( if deps: raise InstallError(
"Cannot uninstall %s. The following installed packages depend on it: %s" "Cannot uninstall %s. The following installed packages depend on it: %s"
% (self.name, deps)) % (self.name, deps))
...@@ -817,7 +844,32 @@ def print_pkg(message): ...@@ -817,7 +844,32 @@ def print_pkg(message):
print message print message
class InvalidPackageDependencyError(spack.error.SpackError):
class FetchError(spack.error.SpackError):
"""Raised when something goes wrong during fetch."""
def __init__(self, message, long_msg=None):
super(FetchError, self).__init__(message, long_msg)
class ChecksumError(FetchError):
"""Raised when archive fails to checksum."""
def __init__(self, message, long_msg):
super(ChecksumError, self).__init__(message, long_msg)
class InstallError(spack.error.SpackError):
"""Raised when something goes wrong during install or uninstall."""
def __init__(self, message, long_msg=None):
super(InstallError, self).__init__(message, long_msg)
class PackageError(spack.error.SpackError):
"""Raised when something is wrong with a package definition."""
def __init__(self, message, long_msg=None):
super(PackageError, self).__init__(message, long_msg)
class InvalidPackageDependencyError(PackageError):
"""Raised when package specification is inconsistent with requirements of """Raised when package specification is inconsistent with requirements of
its dependencies.""" its dependencies."""
def __init__(self, message): def __init__(self, message):
......
...@@ -93,6 +93,6 @@ def test_install_and_uninstall(self): ...@@ -93,6 +93,6 @@ def test_install_and_uninstall(self):
try: try:
pkg.do_install() pkg.do_install()
pkg.do_uninstall() pkg.do_uninstall()
except: except Exception, e:
if pkg: pkg.remove_prefix() if pkg: pkg.remove_prefix()
raise raise
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment