From bd1a0a9ad4c9ac19a50c60f75aba6588c8ccfe6c Mon Sep 17 00:00:00 2001 From: Greg Becker Date: Sun, 7 Jun 2020 21:43:52 -0700 Subject: view remove: directly check whether specs own files before removing from view (#16955) Bugfix for hardlinks and copies --- lib/spack/spack/filesystem_view.py | 34 +++++++++++++++++++++++++++++----- lib/spack/spack/test/cmd/view.py | 15 +++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/filesystem_view.py b/lib/spack/spack/filesystem_view.py index f4d77a694b..0f21e4d975 100644 --- a/lib/spack/spack/filesystem_view.py +++ b/lib/spack/spack/filesystem_view.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import filecmp import functools as ft import os import re @@ -18,6 +17,7 @@ from llnl.util.filesystem import ( mkdirp, remove_dead_links, remove_empty_directories) import spack.util.spack_yaml as s_yaml +import spack.util.spack_json as s_json import spack.spec import spack.store @@ -411,10 +411,34 @@ class YamlFilesystemView(FilesystemView): if not os.path.lexists(dest): tty.warn("Tried to remove %s which does not exist" % dest) return - # remove if dest is a hardlink/symlink to src; this will only - # be false if two packages are merged into a prefix and have a - # conflicting file - if filecmp.cmp(src, dest, shallow=True): + + def needs_file(spec, file): + # convert the file we want to remove to a source in this spec + projection = self.get_projection_for_spec(spec) + relative_path = os.path.relpath(file, projection) + test_path = os.path.join(spec.prefix, relative_path) + + # check if this spec owns a file of that name (through the + # manifest in the metadata dir, which we have in the view). + manifest_file = os.path.join(self.get_path_meta_folder(spec), + spack.store.layout.manifest_file_name) + try: + with open(manifest_file, 'r') as f: + manifest = s_json.load(f) + except (OSError, IOError): + # if we can't load it, assume it doesn't know about the file. + manifest = {} + return test_path in manifest + + # remove if dest is not owned by any other package in the view + # This will only be false if two packages are merged into a prefix + # and have a conflicting file + + # check all specs for whether they own the file. That include the spec + # we are currently removing, as we remove files before unlinking the + # metadata directory. + if len([s for s in self.get_all_specs() + if needs_file(s, dest)]) <= 1: os.remove(dest) def check_added(self, spec): diff --git a/lib/spack/spack/test/cmd/view.py b/lib/spack/spack/test/cmd/view.py index d908248a19..4ff1592035 100644 --- a/lib/spack/spack/test/cmd/view.py +++ b/lib/spack/spack/test/cmd/view.py @@ -40,6 +40,21 @@ def test_view_link_type( assert os.path.islink(package_prefix) == is_link_cmd +@pytest.mark.parametrize('add_cmd', ['hardlink', 'symlink', 'hard', 'add', + 'copy', 'relocate']) +def test_view_link_type_remove( + tmpdir, mock_packages, mock_archive, mock_fetch, config, + install_mockery, add_cmd): + install('needs-relocation') + viewpath = str(tmpdir.mkdir('view_{0}'.format(add_cmd))) + view(add_cmd, viewpath, 'needs-relocation') + bindir = os.path.join(viewpath, 'bin') + assert os.path.exists(bindir) + + view('remove', viewpath, 'needs-relocation') + assert not os.path.exists(bindir) + + @pytest.mark.parametrize('cmd', ['hardlink', 'symlink', 'hard', 'add', 'copy', 'relocate']) def test_view_projections( -- cgit v1.2.3-60-g2f50