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
This commit is contained in:
parent
7aa9cb0f7a
commit
3347ef2de4
@ -33,8 +33,6 @@
|
|||||||
YamlFilesystemView.
|
YamlFilesystemView.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
import os
|
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.util.link_tree import MergeConflictError
|
from llnl.util.link_tree import MergeConflictError
|
||||||
from llnl.util.tty.color import colorize
|
from llnl.util.tty.color import colorize
|
||||||
@ -45,13 +43,15 @@
|
|||||||
import spack.schema.projections
|
import spack.schema.projections
|
||||||
from spack.config import validate
|
from spack.config import validate
|
||||||
from spack.filesystem_view import YamlFilesystemView
|
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
|
from spack.util import spack_yaml as s_yaml
|
||||||
|
|
||||||
description = "project packages to a compact naming scheme on the filesystem."
|
description = "project packages to a compact naming scheme on the filesystem."
|
||||||
section = "environments"
|
section = "environments"
|
||||||
level = "short"
|
level = "short"
|
||||||
|
|
||||||
actions_link = ["symlink", "add", "soft", "hardlink", "hard"]
|
actions_link = ["symlink", "add", "soft", "hardlink", "hard", "copy",
|
||||||
|
"relocate"]
|
||||||
actions_remove = ["remove", "rm"]
|
actions_remove = ["remove", "rm"]
|
||||||
actions_status = ["statlink", "status", "check"]
|
actions_status = ["statlink", "status", "check"]
|
||||||
|
|
||||||
@ -112,6 +112,9 @@ def setup_parser(sp):
|
|||||||
"hardlink": ssp.add_parser(
|
"hardlink": ssp.add_parser(
|
||||||
'hardlink', aliases=['hard'],
|
'hardlink', aliases=['hard'],
|
||||||
help='add packages files to a filesystem view via hard links'),
|
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": ssp.add_parser(
|
||||||
'remove', aliases=['rm'],
|
'remove', aliases=['rm'],
|
||||||
help='remove packages from a filesystem view'),
|
help='remove packages from a filesystem view'),
|
||||||
@ -125,7 +128,7 @@ def setup_parser(sp):
|
|||||||
act.add_argument('path', nargs=1,
|
act.add_argument('path', nargs=1,
|
||||||
help="path to file system view directory")
|
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
|
# invalid for remove/statlink, for those commands the view needs to
|
||||||
# already know its own projections.
|
# already know its own projections.
|
||||||
help_msg = "Initialize view using projections from file."
|
help_msg = "Initialize view using projections from file."
|
||||||
@ -157,7 +160,7 @@ def setup_parser(sp):
|
|||||||
so["nargs"] = "+"
|
so["nargs"] = "+"
|
||||||
act.add_argument('specs', **so)
|
act.add_argument('specs', **so)
|
||||||
|
|
||||||
for cmd in ["symlink", "hardlink"]:
|
for cmd in ["symlink", "hardlink", "copy"]:
|
||||||
act = file_system_view_actions[cmd]
|
act = file_system_view_actions[cmd]
|
||||||
act.add_argument("-i", "--ignore-conflicts", action='store_true')
|
act.add_argument("-i", "--ignore-conflicts", action='store_true')
|
||||||
|
|
||||||
@ -179,11 +182,19 @@ def view(parser, args):
|
|||||||
else:
|
else:
|
||||||
ordered_projections = {}
|
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(
|
view = YamlFilesystemView(
|
||||||
path, spack.store.layout,
|
path, spack.store.layout,
|
||||||
projections=ordered_projections,
|
projections=ordered_projections,
|
||||||
ignore_conflicts=getattr(args, "ignore_conflicts", False),
|
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)
|
verbose=args.verbose)
|
||||||
|
|
||||||
# Process common args and specs
|
# Process common args and specs
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
import spack.schema.projections
|
import spack.schema.projections
|
||||||
import spack.projections
|
import spack.projections
|
||||||
import spack.config
|
import spack.config
|
||||||
|
import spack.relocate
|
||||||
from spack.error import SpackError
|
from spack.error import SpackError
|
||||||
from spack.directory_layout import ExtensionAlreadyInstalledError
|
from spack.directory_layout import ExtensionAlreadyInstalledError
|
||||||
from spack.directory_layout import YamlViewExtensionsLayout
|
from spack.directory_layout import YamlViewExtensionsLayout
|
||||||
@ -41,6 +42,58 @@
|
|||||||
_projections_path = '.spack/projections.yaml'
|
_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):
|
class FilesystemView(object):
|
||||||
"""
|
"""
|
||||||
Governs a filesystem view that is located at certain root-directory.
|
Governs a filesystem view that is located at certain root-directory.
|
||||||
@ -67,9 +120,12 @@ def __init__(self, root, layout, **kwargs):
|
|||||||
self.projections = kwargs.get('projections', {})
|
self.projections = kwargs.get('projections', {})
|
||||||
|
|
||||||
self.ignore_conflicts = kwargs.get("ignore_conflicts", False)
|
self.ignore_conflicts = kwargs.get("ignore_conflicts", False)
|
||||||
self.link = kwargs.get("link", os.symlink)
|
|
||||||
self.verbose = kwargs.get("verbose", False)
|
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):
|
def add_specs(self, *specs, **kwargs):
|
||||||
"""
|
"""
|
||||||
Add given specs to view.
|
Add given specs to view.
|
||||||
@ -355,8 +411,6 @@ def remove_file(self, src, dest):
|
|||||||
if not os.path.lexists(dest):
|
if not os.path.lexists(dest):
|
||||||
tty.warn("Tried to remove %s which does not exist" % dest)
|
tty.warn("Tried to remove %s which does not exist" % dest)
|
||||||
return
|
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
|
# 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
|
# be false if two packages are merged into a prefix and have a
|
||||||
# conflicting file
|
# conflicting file
|
||||||
|
@ -332,7 +332,7 @@ def add_files_to_view(self, view, merge_map):
|
|||||||
"""
|
"""
|
||||||
for src, dst in merge_map.items():
|
for src, dst in merge_map.items():
|
||||||
if not os.path.exists(dst):
|
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):
|
def remove_files_from_view(self, view, merge_map):
|
||||||
"""Given a map of package files to files currently linked in the view,
|
"""Given a map of package files to files currently linked in the view,
|
||||||
|
@ -24,7 +24,8 @@ def create_projection_file(tmpdir, projection):
|
|||||||
return projection_file
|
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(
|
def test_view_link_type(
|
||||||
tmpdir, mock_packages, mock_archive, mock_fetch, config,
|
tmpdir, mock_packages, mock_archive, mock_fetch, config,
|
||||||
install_mockery, cmd):
|
install_mockery, cmd):
|
||||||
@ -33,10 +34,14 @@ def test_view_link_type(
|
|||||||
view(cmd, viewpath, 'libdwarf')
|
view(cmd, viewpath, 'libdwarf')
|
||||||
package_prefix = os.path.join(viewpath, 'libdwarf')
|
package_prefix = os.path.join(viewpath, 'libdwarf')
|
||||||
assert os.path.exists(package_prefix)
|
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(
|
def test_view_projections(
|
||||||
tmpdir, mock_packages, mock_archive, mock_fetch, config,
|
tmpdir, mock_packages, mock_archive, mock_fetch, config,
|
||||||
install_mockery, cmd):
|
install_mockery, cmd):
|
||||||
@ -54,7 +59,10 @@ def test_view_projections(
|
|||||||
|
|
||||||
package_prefix = os.path.join(viewpath, 'libdwarf-20130207/libdwarf')
|
package_prefix = os.path.join(viewpath, 'libdwarf-20130207/libdwarf')
|
||||||
assert os.path.exists(package_prefix)
|
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(
|
def test_view_multiple_projections(
|
||||||
|
@ -1521,7 +1521,7 @@ _spack_view() {
|
|||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help -v --verbose -e --exclude -d --dependencies"
|
SPACK_COMPREPLY="-h --help -v --verbose -e --exclude -d --dependencies"
|
||||||
else
|
else
|
||||||
SPACK_COMPREPLY="symlink add soft hardlink hard remove rm statlink status check"
|
SPACK_COMPREPLY="symlink add soft hardlink hard copy relocate remove rm statlink status check"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1570,6 +1570,24 @@ _spack_view_hard() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_spack_view_copy() {
|
||||||
|
if $list_options
|
||||||
|
then
|
||||||
|
SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts"
|
||||||
|
else
|
||||||
|
_all_packages
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_spack_view_relocate() {
|
||||||
|
if $list_options
|
||||||
|
then
|
||||||
|
SPACK_COMPREPLY="-h --help --projection-file -i --ignore-conflicts"
|
||||||
|
else
|
||||||
|
_all_packages
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
_spack_view_remove() {
|
_spack_view_remove() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
|
@ -962,7 +962,7 @@ def add_files_to_view(self, view, merge_map):
|
|||||||
bin_dir = self.spec.prefix.bin
|
bin_dir = self.spec.prefix.bin
|
||||||
for src, dst in merge_map.items():
|
for src, dst in merge_map.items():
|
||||||
if not path_contains_subdirectory(src, bin_dir):
|
if not path_contains_subdirectory(src, bin_dir):
|
||||||
view.link(src, dst)
|
view.link(src, dst, spec=self.spec)
|
||||||
elif not os.path.islink(src):
|
elif not os.path.islink(src):
|
||||||
copy(src, dst)
|
copy(src, dst)
|
||||||
if 'script' in get_filetype(src):
|
if 'script' in get_filetype(src):
|
||||||
@ -988,7 +988,7 @@ def add_files_to_view(self, view, merge_map):
|
|||||||
orig_link_target = os.path.join(self.spec.prefix, realpath_rel)
|
orig_link_target = os.path.join(self.spec.prefix, realpath_rel)
|
||||||
|
|
||||||
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
||||||
view.link(new_link_target, dst)
|
view.link(new_link_target, dst, spec=self.spec)
|
||||||
|
|
||||||
def remove_files_from_view(self, view, merge_map):
|
def remove_files_from_view(self, view, merge_map):
|
||||||
bin_dir = self.spec.prefix.bin
|
bin_dir = self.spec.prefix.bin
|
||||||
|
Loading…
Reference in New Issue
Block a user