From a81ec88c6cfc009346232fbb26b807bb47c64430 Mon Sep 17 00:00:00 2001 From: psakievich Date: Mon, 16 Aug 2021 20:21:57 -0600 Subject: Allow environment views to be sym/hard link and copy types (#24832) Add link type to spack.yaml format Add tests to verify link behavior is correct for installed files for all three view types Co-authored-by: vsoch --- lib/spack/spack/cmd/view.py | 15 ++++----------- lib/spack/spack/environment.py | 20 +++++++++++++++----- lib/spack/spack/filesystem_view.py | 23 +++++++++++++++++++++++ lib/spack/spack/schema/env.py | 3 +++ lib/spack/spack/test/cmd/env.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 16 deletions(-) diff --git a/lib/spack/spack/cmd/view.py b/lib/spack/spack/cmd/view.py index 7c745a4da0..f795c17a1c 100644 --- a/lib/spack/spack/cmd/view.py +++ b/lib/spack/spack/cmd/view.py @@ -42,12 +42,7 @@ import spack.environment as ev import spack.schema.projections import spack.store from spack.config import validate -from spack.filesystem_view import ( - YamlFilesystemView, - view_copy, - view_hardlink, - view_symlink, -) +from spack.filesystem_view import YamlFilesystemView, view_func_parser from spack.util import spack_yaml as s_yaml description = "project packages to a compact naming scheme on the filesystem." @@ -187,12 +182,10 @@ def view(parser, args): 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 + if args.action in actions_link: + link_fn = view_func_parser(args.action) else: - link_fn = view_symlink + link_fn = view_func_parser('symlink') view = YamlFilesystemView( path, spack.store.layout, diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py index cc5a066abb..6fa7f62553 100644 --- a/lib/spack/spack/environment.py +++ b/lib/spack/spack/environment.py @@ -34,7 +34,11 @@ import spack.util.lock as lk import spack.util.path import spack.util.spack_json as sjson import spack.util.spack_yaml as syaml -from spack.filesystem_view import YamlFilesystemView +from spack.filesystem_view import ( + YamlFilesystemView, + inverse_view_func_parser, + view_func_parser, +) from spack.spec import Spec from spack.spec_list import InvalidSpecConstraintError, SpecList from spack.util.path import substitute_path_variables @@ -456,12 +460,13 @@ def _eval_conditional(string): class ViewDescriptor(object): def __init__(self, base_path, root, projections={}, select=[], exclude=[], - link=default_view_link): + link=default_view_link, link_type='symlink'): self.base = base_path self.root = spack.util.path.canonicalize_path(root) self.projections = projections self.select = select self.exclude = exclude + self.link_type = view_func_parser(link_type) self.link = link def select_fn(self, spec): @@ -475,7 +480,8 @@ class ViewDescriptor(object): self.projections == other.projections, self.select == other.select, self.exclude == other.exclude, - self.link == other.link]) + self.link == other.link, + self.link_type == other.link_type]) def to_dict(self): ret = syaml.syaml_dict([('root', self.root)]) @@ -490,6 +496,8 @@ class ViewDescriptor(object): ret['select'] = self.select if self.exclude: ret['exclude'] = self.exclude + if self.link_type: + ret['link_type'] = inverse_view_func_parser(self.link_type) if self.link != default_view_link: ret['link'] = self.link return ret @@ -501,7 +509,8 @@ class ViewDescriptor(object): d.get('projections', {}), d.get('select', []), d.get('exclude', []), - d.get('link', default_view_link)) + d.get('link', default_view_link), + d.get('link_type', 'symlink')) @property def _current_root(self): @@ -565,7 +574,8 @@ class ViewDescriptor(object): raise SpackEnvironmentViewError(msg) return YamlFilesystemView(root, spack.store.layout, ignore_conflicts=True, - projections=self.projections) + projections=self.projections, + link=self.link_type) def __contains__(self, spec): """Is the spec described by the view descriptor diff --git a/lib/spack/spack/filesystem_view.py b/lib/spack/spack/filesystem_view.py index dd872d712f..68cf7c156c 100644 --- a/lib/spack/spack/filesystem_view.py +++ b/lib/spack/spack/filesystem_view.py @@ -98,6 +98,29 @@ def view_copy(src, dst, view, spec=None): ) +def view_func_parser(parsed_name): + # What method are we using for this view + if parsed_name in ("hardlink", "hard"): + return view_hardlink + elif parsed_name in ("copy", "relocate"): + return view_copy + elif parsed_name in ("add", "symlink", "soft"): + return view_symlink + else: + raise ValueError("invalid link type for view: '%s'" % parsed_name) + + +def inverse_view_func_parser(view_type): + # get string based on view type + if view_type is view_hardlink: + link_name = 'hardlink' + elif view_type is view_copy: + link_name = 'copy' + else: + link_name = 'symlink' + return link_name + + class FilesystemView(object): """ Governs a filesystem view that is located at certain root-directory. diff --git a/lib/spack/spack/schema/env.py b/lib/spack/spack/schema/env.py index 82971505e4..de6a5d9568 100644 --- a/lib/spack/spack/schema/env.py +++ b/lib/spack/spack/schema/env.py @@ -126,6 +126,9 @@ schema = { 'type': 'string', 'pattern': '(roots|all)', }, + 'link_type': { + 'type': 'string' + }, 'select': { 'type': 'array', 'items': { diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py index e680507322..8ead966655 100644 --- a/lib/spack/spack/test/cmd/env.py +++ b/lib/spack/spack/test/cmd/env.py @@ -1948,6 +1948,35 @@ env: (spec.version, spec.compiler.name))) +@pytest.mark.parametrize('link_type', ['hardlink', 'copy', 'symlink']) +def test_view_link_type(link_type, tmpdir, mock_fetch, mock_packages, mock_archive, + install_mockery): + filename = str(tmpdir.join('spack.yaml')) + viewdir = str(tmpdir.join('view')) + with open(filename, 'w') as f: + f.write("""\ +env: + specs: + - mpileaks + view: + default: + root: %s + link_type: %s""" % (viewdir, link_type)) + with tmpdir.as_cwd(): + env('create', 'test', './spack.yaml') + with ev.read('test'): + install() + + test = ev.read('test') + + for spec in test.roots(): + file_path = test.default_view.view()._root + file_to_test = os.path.join( + file_path, spec.name) + assert os.path.isfile(file_to_test) + assert os.path.islink(file_to_test) == (link_type == 'symlink') + + def test_view_link_all(tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery): filename = str(tmpdir.join('spack.yaml')) -- cgit v1.2.3-60-g2f50