Fixes #502. Create install prefix before build, clean up do_install.
- Fix bug introduced during merge of stage refactor. - install prefix was not created before build_environment.fork() - build_environment.fork() calls setup_dependent_environment - python's setup_dependent_environment can inadvertently create the install prefix before directory_layout expects it. - Clean up Package.do_install: - simplify control flow: parent process now entirely responsible for creating/destroying the install prefix. cleanup is now in one place. - Hoisting cleanup out of the child improves nesting of try/catch in `real_work`. - `real_work` renamed to `build_process`
This commit is contained in:
parent
18ce5ccf8f
commit
5aadb6df19
2 changed files with 91 additions and 80 deletions
|
@ -85,6 +85,16 @@ def create_install_directory(self, spec):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
def check_installed(self, spec):
|
||||||
|
"""Checks whether a spec is installed.
|
||||||
|
|
||||||
|
Return the spec's prefix, if it is installed, None otherwise.
|
||||||
|
|
||||||
|
Raise an exception if the install is inconsistent or corrupt.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
def extension_map(self, spec):
|
def extension_map(self, spec):
|
||||||
"""Get a dict of currently installed extension packages for a spec.
|
"""Get a dict of currently installed extension packages for a spec.
|
||||||
|
|
||||||
|
@ -246,26 +256,36 @@ def build_packages_path(self, spec):
|
||||||
def create_install_directory(self, spec):
|
def create_install_directory(self, spec):
|
||||||
_check_concrete(spec)
|
_check_concrete(spec)
|
||||||
|
|
||||||
|
prefix = self.check_installed(spec)
|
||||||
|
if prefix:
|
||||||
|
raise InstallDirectoryAlreadyExistsError(prefix)
|
||||||
|
|
||||||
|
mkdirp(self.metadata_path(spec))
|
||||||
|
self.write_spec(spec, self.spec_file_path(spec))
|
||||||
|
|
||||||
|
|
||||||
|
def check_installed(self, spec):
|
||||||
|
_check_concrete(spec)
|
||||||
path = self.path_for_spec(spec)
|
path = self.path_for_spec(spec)
|
||||||
spec_file_path = self.spec_file_path(spec)
|
spec_file_path = self.spec_file_path(spec)
|
||||||
|
|
||||||
if os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
if not os.path.isfile(spec_file_path):
|
return None
|
||||||
raise InconsistentInstallDirectoryError(
|
|
||||||
'No spec file found at path %s' % spec_file_path)
|
|
||||||
|
|
||||||
installed_spec = self.read_spec(spec_file_path)
|
if not os.path.isfile(spec_file_path):
|
||||||
if installed_spec == self.spec:
|
raise InconsistentInstallDirectoryError(
|
||||||
raise InstallDirectoryAlreadyExistsError(path)
|
'Inconsistent state: install prefix exists but contains no spec.yaml:',
|
||||||
|
" " + path)
|
||||||
|
|
||||||
if spec.dag_hash() == installed_spec.dag_hash():
|
installed_spec = self.read_spec(spec_file_path)
|
||||||
raise SpecHashCollisionError(installed_hash, spec_hash)
|
if installed_spec == spec:
|
||||||
else:
|
return path
|
||||||
raise InconsistentInstallDirectoryError(
|
|
||||||
'Spec file in %s does not match hash!' % spec_file_path)
|
|
||||||
|
|
||||||
mkdirp(self.metadata_path(spec))
|
if spec.dag_hash() == installed_spec.dag_hash():
|
||||||
self.write_spec(spec, spec_file_path)
|
raise SpecHashCollisionError(installed_hash, spec_hash)
|
||||||
|
else:
|
||||||
|
raise InconsistentInstallDirectoryError(
|
||||||
|
'Spec file in %s does not match hash!' % spec_file_path)
|
||||||
|
|
||||||
|
|
||||||
def all_specs(self):
|
def all_specs(self):
|
||||||
|
@ -399,8 +419,8 @@ def remove_extension(self, spec, ext_spec):
|
||||||
|
|
||||||
class DirectoryLayoutError(SpackError):
|
class DirectoryLayoutError(SpackError):
|
||||||
"""Superclass for directory layout errors."""
|
"""Superclass for directory layout errors."""
|
||||||
def __init__(self, message):
|
def __init__(self, message, long_msg=None):
|
||||||
super(DirectoryLayoutError, self).__init__(message)
|
super(DirectoryLayoutError, self).__init__(message, long_msg)
|
||||||
|
|
||||||
|
|
||||||
class SpecHashCollisionError(DirectoryLayoutError):
|
class SpecHashCollisionError(DirectoryLayoutError):
|
||||||
|
@ -422,8 +442,8 @@ def __init__(self, installed_spec, prefix, error):
|
||||||
|
|
||||||
class InconsistentInstallDirectoryError(DirectoryLayoutError):
|
class InconsistentInstallDirectoryError(DirectoryLayoutError):
|
||||||
"""Raised when a package seems to be installed to the wrong place."""
|
"""Raised when a package seems to be installed to the wrong place."""
|
||||||
def __init__(self, message):
|
def __init__(self, message, long_msg=None):
|
||||||
super(InconsistentInstallDirectoryError, self).__init__(message)
|
super(InconsistentInstallDirectoryError, self).__init__(message, long_msg)
|
||||||
|
|
||||||
|
|
||||||
class InstallDirectoryAlreadyExistsError(DirectoryLayoutError):
|
class InstallDirectoryAlreadyExistsError(DirectoryLayoutError):
|
||||||
|
|
|
@ -845,7 +845,8 @@ def do_install(self,
|
||||||
if not self.spec.concrete:
|
if not self.spec.concrete:
|
||||||
raise ValueError("Can only install concrete packages.")
|
raise ValueError("Can only install concrete packages.")
|
||||||
|
|
||||||
if os.path.exists(self.prefix):
|
# Ensure package is not already installed
|
||||||
|
if spack.install_layout.check_installed(self.spec):
|
||||||
tty.msg("%s is already installed in %s" % (self.name, self.prefix))
|
tty.msg("%s is already installed in %s" % (self.name, self.prefix))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -857,18 +858,11 @@ def do_install(self,
|
||||||
keep_prefix=keep_prefix, keep_stage=keep_stage, ignore_deps=ignore_deps,
|
keep_prefix=keep_prefix, keep_stage=keep_stage, ignore_deps=ignore_deps,
|
||||||
fake=fake, skip_patch=skip_patch, verbose=verbose, make_jobs=make_jobs)
|
fake=fake, skip_patch=skip_patch, verbose=verbose, make_jobs=make_jobs)
|
||||||
|
|
||||||
def cleanup():
|
# Set parallelism before starting build.
|
||||||
"""Handles removing install prefix on error."""
|
self.make_jobs = make_jobs
|
||||||
if not keep_prefix:
|
|
||||||
self.remove_prefix()
|
|
||||||
else:
|
|
||||||
tty.warn("Keeping install prefix in place despite error.",
|
|
||||||
"Spack will think this package is installed. " +
|
|
||||||
"Manually remove this directory to fix:",
|
|
||||||
self.prefix, wrap=True)
|
|
||||||
|
|
||||||
# Then install the package itself.
|
# Then install the package itself.
|
||||||
def real_work():
|
def build_process():
|
||||||
"""Forked for each build. Has its own process and python
|
"""Forked for each build. Has its own process and python
|
||||||
module space set up by build_environment.fork()."""
|
module space set up by build_environment.fork()."""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
@ -878,30 +872,24 @@ def real_work():
|
||||||
else:
|
else:
|
||||||
self.do_stage()
|
self.do_stage()
|
||||||
|
|
||||||
# create the install directory. The install layout
|
tty.msg("Building %s" % self.name)
|
||||||
# handles this in case so that it can use whatever
|
|
||||||
# package naming scheme it likes.
|
|
||||||
spack.install_layout.create_install_directory(self.spec)
|
|
||||||
|
|
||||||
try:
|
self.stage.keep = keep_stage
|
||||||
tty.msg("Building %s" % self.name)
|
with self.stage:
|
||||||
|
# Run the pre-install hook in the child process after
|
||||||
|
# the directory is created.
|
||||||
|
spack.hooks.pre_install(self)
|
||||||
|
|
||||||
self.stage.keep = keep_stage
|
if fake:
|
||||||
with self.stage:
|
self.do_fake_install()
|
||||||
# Run the pre-install hook in the child process after
|
else:
|
||||||
# the directory is created.
|
# Do the real install in the source directory.
|
||||||
spack.hooks.pre_install(self)
|
self.stage.chdir_to_source()
|
||||||
|
|
||||||
if fake:
|
# Save the build environment in a file before building.
|
||||||
self.do_fake_install()
|
env_path = join_path(os.getcwd(), 'spack-build.env')
|
||||||
|
|
||||||
else:
|
|
||||||
# Do the real install in the source directory.
|
|
||||||
self.stage.chdir_to_source()
|
|
||||||
|
|
||||||
# Save the build environment in a file before building.
|
|
||||||
env_path = join_path(os.getcwd(), 'spack-build.env')
|
|
||||||
|
|
||||||
|
try:
|
||||||
# Redirect I/O to a build log (and optionally to the terminal)
|
# Redirect I/O to a build log (and optionally to the terminal)
|
||||||
log_path = join_path(os.getcwd(), 'spack-build.out')
|
log_path = join_path(os.getcwd(), 'spack-build.out')
|
||||||
log_file = open(log_path, 'w')
|
log_file = open(log_path, 'w')
|
||||||
|
@ -909,43 +897,46 @@ def real_work():
|
||||||
dump_environment(env_path)
|
dump_environment(env_path)
|
||||||
self.install(self.spec, self.prefix)
|
self.install(self.spec, self.prefix)
|
||||||
|
|
||||||
# Ensure that something was actually installed.
|
except ProcessError as e:
|
||||||
self._sanity_check_install()
|
# Annotate ProcessErrors with the location of the build log.
|
||||||
|
e.build_log = log_path
|
||||||
|
raise e
|
||||||
|
|
||||||
# Copy provenance into the install directory on success
|
# Ensure that something was actually installed.
|
||||||
log_install_path = spack.install_layout.build_log_path(self.spec)
|
self._sanity_check_install()
|
||||||
env_install_path = spack.install_layout.build_env_path(self.spec)
|
|
||||||
packages_dir = spack.install_layout.build_packages_path(self.spec)
|
|
||||||
|
|
||||||
install(log_path, log_install_path)
|
# Copy provenance into the install directory on success
|
||||||
install(env_path, env_install_path)
|
log_install_path = spack.install_layout.build_log_path(self.spec)
|
||||||
dump_packages(self.spec, packages_dir)
|
env_install_path = spack.install_layout.build_env_path(self.spec)
|
||||||
|
packages_dir = spack.install_layout.build_packages_path(self.spec)
|
||||||
|
|
||||||
# Stop timer.
|
install(log_path, log_install_path)
|
||||||
self._total_time = time.time() - start_time
|
install(env_path, env_install_path)
|
||||||
build_time = self._total_time - self._fetch_time
|
dump_packages(self.spec, packages_dir)
|
||||||
|
|
||||||
tty.msg("Successfully installed %s" % self.name,
|
# Stop timer.
|
||||||
"Fetch: %s. Build: %s. Total: %s."
|
self._total_time = time.time() - start_time
|
||||||
% (_hms(self._fetch_time), _hms(build_time), _hms(self._total_time)))
|
build_time = self._total_time - self._fetch_time
|
||||||
print_pkg(self.prefix)
|
|
||||||
|
|
||||||
except ProcessError as e:
|
tty.msg("Successfully installed %s" % self.name,
|
||||||
# Annotate with location of build log.
|
"Fetch: %s. Build: %s. Total: %s."
|
||||||
e.build_log = log_path
|
% (_hms(self._fetch_time), _hms(build_time), _hms(self._total_time)))
|
||||||
cleanup()
|
print_pkg(self.prefix)
|
||||||
raise e
|
|
||||||
|
|
||||||
except:
|
try:
|
||||||
# other exceptions just clean up and raise.
|
# Create the install prefix and fork the build process.
|
||||||
cleanup()
|
spack.install_layout.create_install_directory(self.spec)
|
||||||
raise
|
spack.build_environment.fork(self, build_process)
|
||||||
|
except:
|
||||||
# Set parallelism before starting build.
|
# remove the install prefix if anything went wrong during install.
|
||||||
self.make_jobs = make_jobs
|
if not keep_prefix:
|
||||||
|
self.remove_prefix()
|
||||||
# Do the build.
|
else:
|
||||||
spack.build_environment.fork(self, real_work)
|
tty.warn("Keeping install prefix in place despite error.",
|
||||||
|
"Spack will think this package is installed. " +
|
||||||
|
"Manually remove this directory to fix:",
|
||||||
|
self.prefix, wrap=True)
|
||||||
|
raise
|
||||||
|
|
||||||
# note: PARENT of the build process adds the new package to
|
# note: PARENT of the build process adds the new package to
|
||||||
# the database, so that we don't need to re-read from file.
|
# the database, so that we don't need to re-read from file.
|
||||||
|
|
Loading…
Reference in a new issue