summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGreg Becker <becker33@llnl.gov>2020-06-03 09:45:13 -0700
committerGitHub <noreply@github.com>2020-06-03 09:45:13 -0700
commit3347ef2de4e08374750eb68f750800c1854d595f (patch)
treec98cf95a8322592606adc059493547ac2dcfceb9 /lib
parent7aa9cb0f7a40639852dedb00cc2ecff847a3413b (diff)
downloadspack-3347ef2de4e08374750eb68f750800c1854d595f.tar.gz
spack-3347ef2de4e08374750eb68f750800c1854d595f.tar.bz2
spack-3347ef2de4e08374750eb68f750800c1854d595f.tar.xz
spack-3347ef2de4e08374750eb68f750800c1854d595f.zip
Feature: add option to create view by copying/relocating files (#16480)
* add subcommand `spack view copy/relocate` * update bash completions * add copy/relocate commands to view tests * allow copied views to be removed
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/cmd/view.py23
-rw-r--r--lib/spack/spack/filesystem_view.py60
-rw-r--r--lib/spack/spack/package.py2
-rw-r--r--lib/spack/spack/test/cmd/view.py16
4 files changed, 87 insertions, 14 deletions
diff --git a/lib/spack/spack/cmd/view.py b/lib/spack/spack/cmd/view.py
index bad155a456..151f6c1564 100644
--- a/lib/spack/spack/cmd/view.py
+++ b/lib/spack/spack/cmd/view.py
@@ -33,8 +33,6 @@ All operations on views are performed via proxy objects such as
YamlFilesystemView.
'''
-import os
-
import llnl.util.tty as tty
from llnl.util.link_tree import MergeConflictError
from llnl.util.tty.color import colorize
@@ -45,13 +43,15 @@ import spack.store
import spack.schema.projections
from spack.config import validate
from spack.filesystem_view import YamlFilesystemView
+from spack.filesystem_view import view_symlink, view_hardlink, view_copy
from spack.util import spack_yaml as s_yaml
description = "project packages to a compact naming scheme on the filesystem."
section = "environments"
level = "short"
-actions_link = ["symlink", "add", "soft", "hardlink", "hard"]
+actions_link = ["symlink", "add", "soft", "hardlink", "hard", "copy",
+ "relocate"]
actions_remove = ["remove", "rm"]
actions_status = ["statlink", "status", "check"]
@@ -112,6 +112,9 @@ def setup_parser(sp):
"hardlink": ssp.add_parser(
'hardlink', aliases=['hard'],
help='add packages files to a filesystem view via hard links'),
+ "copy": ssp.add_parser(
+ 'copy', aliases=['relocate'],
+ help='add package files to a filesystem view via copy/relocate'),
"remove": ssp.add_parser(
'remove', aliases=['rm'],
help='remove packages from a filesystem view'),
@@ -125,7 +128,7 @@ def setup_parser(sp):
act.add_argument('path', nargs=1,
help="path to file system view directory")
- if cmd in ("symlink", "hardlink"):
+ if cmd in ("symlink", "hardlink", "copy"):
# invalid for remove/statlink, for those commands the view needs to
# already know its own projections.
help_msg = "Initialize view using projections from file."
@@ -157,7 +160,7 @@ def setup_parser(sp):
so["nargs"] = "+"
act.add_argument('specs', **so)
- for cmd in ["symlink", "hardlink"]:
+ for cmd in ["symlink", "hardlink", "copy"]:
act = file_system_view_actions[cmd]
act.add_argument("-i", "--ignore-conflicts", action='store_true')
@@ -179,11 +182,19 @@ def view(parser, args):
else:
ordered_projections = {}
+ # What method are we using for this view
+ if args.action in ("hardlink", "hard"):
+ link_fn = view_hardlink
+ elif args.action in ("copy", "relocate"):
+ link_fn = view_copy
+ else:
+ link_fn = view_symlink
+
view = YamlFilesystemView(
path, spack.store.layout,
projections=ordered_projections,
ignore_conflicts=getattr(args, "ignore_conflicts", False),
- link=os.link if args.action in ["hardlink", "hard"] else os.symlink,
+ link=link_fn,
verbose=args.verbose)
# Process common args and specs
diff --git a/lib/spack/spack/filesystem_view.py b/lib/spack/spack/filesystem_view.py
index b2bc30e1a5..f4d77a694b 100644
--- a/lib/spack/spack/filesystem_view.py
+++ b/lib/spack/spack/filesystem_view.py
@@ -24,6 +24,7 @@ import spack.store
import spack.schema.projections
import spack.projections
import spack.config
+import spack.relocate
from spack.error import SpackError
from spack.directory_layout import ExtensionAlreadyInstalledError
from spack.directory_layout import YamlViewExtensionsLayout
@@ -41,6 +42,58 @@ __all__ = ["FilesystemView", "YamlFilesystemView"]
_projections_path = '.spack/projections.yaml'
+def view_symlink(src, dst, **kwargs):
+ # keyword arguments are irrelevant
+ # here to fit required call signature
+ os.symlink(src, dst)
+
+
+def view_hardlink(src, dst, **kwargs):
+ # keyword arguments are irrelevant
+ # here to fit required call signature
+ os.link(src, dst)
+
+
+def view_copy(src, dst, view, spec=None):
+ """
+ Copy a file from src to dst.
+
+ Use spec and view to generate relocations
+ """
+ shutil.copyfile(src, dst)
+ if spec:
+ # Not metadata, we have to relocate it
+
+ # Get information on where to relocate from/to
+ prefix_to_projection = dict(
+ (dep.prefix, view.get_projection_for_spec(dep))
+ for dep in spec.traverse()
+ )
+
+ if spack.relocate.is_binary(dst):
+ # relocate binaries
+ spack.relocate.relocate_text_bin(
+ binaries=[dst],
+ orig_install_prefix=spec.prefix,
+ new_install_prefix=view.get_projection_for_spec(spec),
+ orig_spack=spack.paths.spack_root,
+ new_spack=view._root,
+ new_prefixes=prefix_to_projection
+ )
+ else:
+ # relocate text
+ spack.relocate.relocate_text(
+ files=[dst],
+ orig_layout_root=spack.store.layout.root,
+ new_layout_root=view._root,
+ orig_install_prefix=spec.prefix,
+ new_install_prefix=view.get_projection_for_spec(spec),
+ orig_spack=spack.paths.spack_root,
+ new_spack=view._root,
+ new_prefixes=prefix_to_projection
+ )
+
+
class FilesystemView(object):
"""
Governs a filesystem view that is located at certain root-directory.
@@ -67,9 +120,12 @@ class FilesystemView(object):
self.projections = kwargs.get('projections', {})
self.ignore_conflicts = kwargs.get("ignore_conflicts", False)
- self.link = kwargs.get("link", os.symlink)
self.verbose = kwargs.get("verbose", False)
+ # Setup link function to include view
+ link_func = kwargs.get("link", view_symlink)
+ self.link = ft.partial(link_func, view=self)
+
def add_specs(self, *specs, **kwargs):
"""
Add given specs to view.
@@ -355,8 +411,6 @@ class YamlFilesystemView(FilesystemView):
if not os.path.lexists(dest):
tty.warn("Tried to remove %s which does not exist" % dest)
return
- if not os.path.islink(dest):
- raise ValueError("%s is not a link tree!" % dest)
# 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
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 9b6f0efb48..bb5ea41dc3 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -332,7 +332,7 @@ class PackageViewMixin(object):
"""
for src, dst in merge_map.items():
if not os.path.exists(dst):
- view.link(src, dst)
+ view.link(src, dst, spec=self.spec)
def remove_files_from_view(self, view, merge_map):
"""Given a map of package files to files currently linked in the view,
diff --git a/lib/spack/spack/test/cmd/view.py b/lib/spack/spack/test/cmd/view.py
index c52cd12325..d908248a19 100644
--- a/lib/spack/spack/test/cmd/view.py
+++ b/lib/spack/spack/test/cmd/view.py
@@ -24,7 +24,8 @@ def create_projection_file(tmpdir, projection):
return projection_file
-@pytest.mark.parametrize('cmd', ['hardlink', 'symlink', 'hard', 'add'])
+@pytest.mark.parametrize('cmd', ['hardlink', 'symlink', 'hard', 'add',
+ 'copy', 'relocate'])
def test_view_link_type(
tmpdir, mock_packages, mock_archive, mock_fetch, config,
install_mockery, cmd):
@@ -33,10 +34,14 @@ def test_view_link_type(
view(cmd, viewpath, 'libdwarf')
package_prefix = os.path.join(viewpath, 'libdwarf')
assert os.path.exists(package_prefix)
- assert os.path.islink(package_prefix) == (not cmd.startswith('hard'))
+ # Check that we use symlinks for and only for the appropriate subcommands
+ is_link_cmd = cmd in ('symlink', 'add')
+ assert os.path.islink(package_prefix) == is_link_cmd
-@pytest.mark.parametrize('cmd', ['hardlink', 'symlink', 'hard', 'add'])
+
+@pytest.mark.parametrize('cmd', ['hardlink', 'symlink', 'hard', 'add',
+ 'copy', 'relocate'])
def test_view_projections(
tmpdir, mock_packages, mock_archive, mock_fetch, config,
install_mockery, cmd):
@@ -54,7 +59,10 @@ def test_view_projections(
package_prefix = os.path.join(viewpath, 'libdwarf-20130207/libdwarf')
assert os.path.exists(package_prefix)
- assert os.path.islink(package_prefix) == (not cmd.startswith('hard'))
+
+ # Check that we use symlinks for and only for the appropriate subcommands
+ is_symlink_cmd = cmd in ('symlink', 'add')
+ assert os.path.islink(package_prefix) == is_symlink_cmd
def test_view_multiple_projections(