environment.py: clean up broken views on failure (#29643)

When view creation fails for some reason, remove it, so that the next
time around it can start from scratch.
This commit is contained in:
Harmen Stoppels 2022-03-24 09:04:42 +01:00 committed by GitHub
parent 20255b6161
commit 011a8b3f3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 12 deletions

View file

@ -524,22 +524,30 @@ def regenerate(self, concretized_specs):
tty.msg("Updating view at {0}".format(self.root)) tty.msg("Updating view at {0}".format(self.root))
view = self.view(new=new_root) view = self.view(new=new_root)
root_dirname = os.path.dirname(self.root)
tmp_symlink_name = os.path.join(root_dirname, '._view_link')
# Create a new view
try:
fs.mkdirp(new_root) fs.mkdirp(new_root)
view.add_specs(*specs, with_dependencies=False) view.add_specs(*specs, with_dependencies=False)
# create symlink from tmpname to new_root # create symlink from tmp_symlink_name to new_root
root_dirname = os.path.dirname(self.root)
tmp_symlink_name = os.path.join(root_dirname, '._view_link')
if os.path.exists(tmp_symlink_name): if os.path.exists(tmp_symlink_name):
os.unlink(tmp_symlink_name) os.unlink(tmp_symlink_name)
symlink(new_root, tmp_symlink_name) symlink(new_root, tmp_symlink_name)
# mv symlink atomically over root symlink to old_root # mv symlink atomically over root symlink to old_root
if os.path.exists(self.root) and not os.path.islink(self.root):
msg = "Cannot create view: "
msg += "file already exists and is not a link: %s" % self.root
raise SpackEnvironmentViewError(msg)
rename(tmp_symlink_name, self.root) rename(tmp_symlink_name, self.root)
except Exception as e:
# Clean up new view and temporary symlink on any failure.
try:
shutil.rmtree(new_root, ignore_errors=True)
os.unlink(tmp_symlink_name)
except (IOError, OSError):
pass
raise e
# Remove the old root when it's in the same folder as the new root. This guards # Remove the old root when it's in the same folder as the new root. This guards
# against removal of an arbitrary path when the original symlink in self.root # against removal of an arbitrary path when the original symlink in self.root

View file

@ -2746,3 +2746,28 @@ def test_env_view_fail_if_symlink_points_elsewhere(tmpdir, install_mockery, mock
add('libelf') add('libelf')
install('--fake') install('--fake')
assert os.path.isdir(non_view_dir) assert os.path.isdir(non_view_dir)
def test_failed_view_cleanup(tmpdir, mock_stage, mock_fetch, install_mockery):
"""Tests whether Spack cleans up after itself when a view fails to create"""
view = str(tmpdir.join('view'))
with ev.create('env', with_view=view):
add('libelf')
install('--fake')
# Save the current view directory.
resolved_view = os.path.realpath(view)
all_views = os.path.dirname(resolved_view)
views_before = os.listdir(all_views)
# Add a spec that results in MergeConflictError's when creating a view
with ev.read('env'):
add('libelf cflags=-O3')
with pytest.raises(llnl.util.link_tree.MergeConflictError):
install('--fake')
# Make sure there is no broken view in the views directory, and the current
# view is the original view from before the failed regenerate attempt.
views_after = os.listdir(all_views)
assert views_before == views_after
assert os.path.samefile(resolved_view, view)