summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2019-05-23 14:28:28 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2019-05-26 18:23:44 -0700
commitd6f2ff14261da7fe9f98d32d56bb37e52423c75b (patch)
treec75773f39f927ef71d85e8aeb93a9089a31b6675 /lib
parentf32843528e52dc40c6a379f03d69b06f8a52265f (diff)
downloadspack-d6f2ff14261da7fe9f98d32d56bb37e52423c75b.tar.gz
spack-d6f2ff14261da7fe9f98d32d56bb37e52423c75b.tar.bz2
spack-d6f2ff14261da7fe9f98d32d56bb37e52423c75b.tar.xz
spack-d6f2ff14261da7fe9f98d32d56bb37e52423c75b.zip
link_tree: add option to merge link trees with relative targets
- previous version of link trees would only do absolute symlinks - this version can do relative links using merge(relative=True)
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/llnl/util/link_tree.py65
-rw-r--r--lib/spack/spack/test/llnl/util/link_tree.py65
2 files changed, 90 insertions, 40 deletions
diff --git a/lib/spack/llnl/util/link_tree.py b/lib/spack/llnl/util/link_tree.py
index 8b32253297..e9d8e72161 100644
--- a/lib/spack/llnl/util/link_tree.py
+++ b/lib/spack/llnl/util/link_tree.py
@@ -5,6 +5,8 @@
"""LinkTree class for setting up trees of symbolic links."""
+from __future__ import print_function
+
import os
import shutil
import filecmp
@@ -17,6 +19,16 @@ __all__ = ['LinkTree']
empty_file_name = '.spack-empty'
+def remove_link(src, dest):
+ 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
+ if filecmp.cmp(src, dest, shallow=True):
+ os.remove(dest)
+
+
class LinkTree(object):
"""Class to create trees of symbolic links from a source directory.
@@ -100,16 +112,28 @@ class LinkTree(object):
if os.path.exists(marker):
os.remove(marker)
- def merge(self, dest_root, **kwargs):
+ def merge(self, dest_root, ignore_conflicts=False, ignore=None,
+ link=os.symlink, relative=False):
"""Link all files in src into dest, creating directories
if necessary.
- If ignore_conflicts is True, do not break when the target exists but
- rather return a list of files that could not be linked.
- Note that files blocking directories will still cause an error.
+
+ Keyword Args:
+
+ ignore_conflicts (bool): if True, do not break when the target exists;
+ return a list of files that could not be linked
+
+ ignore (callable): callable that returns True if a file is to be
+ ignored in the merge (by default ignore nothing)
+
+ link (callable): function to create links with (defaults to os.symlink)
+
+ relative (bool): create all symlinks relative to the target
+ (default False)
+
"""
- ignore_conflicts = kwargs.get("ignore_conflicts", False)
+ if ignore is None:
+ ignore = lambda x: False
- ignore = kwargs.get('ignore', lambda x: False)
conflict = self.find_conflict(
dest_root, ignore=ignore, ignore_file_conflicts=ignore_conflicts)
if conflict:
@@ -117,42 +141,33 @@ class LinkTree(object):
self.merge_directories(dest_root, ignore)
existing = []
- merge_file = kwargs.get('merge_file', merge_link)
for src, dst in self.get_file_map(dest_root, ignore).items():
if os.path.exists(dst):
existing.append(dst)
+ elif relative:
+ abs_src = os.path.abspath(src)
+ dst_dir = os.path.dirname(os.path.abspath(dst))
+ rel = os.path.relpath(abs_src, dst_dir)
+ link(rel, dst)
else:
- merge_file(src, dst)
+ link(src, dst)
for c in existing:
tty.warn("Could not merge: %s" % c)
- def unmerge(self, dest_root, **kwargs):
+ def unmerge(self, dest_root, ignore=None, remove_file=remove_link):
"""Unlink all files in dest that exist in src.
Unlinks directories in dest if they are empty.
"""
- remove_file = kwargs.get('remove_file', remove_link)
- ignore = kwargs.get('ignore', lambda x: False)
+ if ignore is None:
+ ignore = lambda x: False
+
for src, dst in self.get_file_map(dest_root, ignore).items():
remove_file(src, dst)
self.unmerge_directories(dest_root, ignore)
-def merge_link(src, dest):
- os.symlink(src, dest)
-
-
-def remove_link(src, dest):
- 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
- if filecmp.cmp(src, dest, shallow=True):
- os.remove(dest)
-
-
class MergeConflictError(Exception):
def __init__(self, path):
diff --git a/lib/spack/spack/test/llnl/util/link_tree.py b/lib/spack/spack/test/llnl/util/link_tree.py
index 884e950778..6b00afb3fe 100644
--- a/lib/spack/spack/test/llnl/util/link_tree.py
+++ b/lib/spack/spack/test/llnl/util/link_tree.py
@@ -38,9 +38,11 @@ def link_tree(stage):
return LinkTree(source_path)
-def check_file_link(filename):
+def check_file_link(filename, expected_target):
assert os.path.isfile(filename)
assert os.path.islink(filename)
+ assert (os.path.abspath(os.path.realpath(filename)) ==
+ os.path.abspath(expected_target))
def check_dir(filename):
@@ -51,13 +53,46 @@ def test_merge_to_new_directory(stage, link_tree):
with working_dir(stage.path):
link_tree.merge('dest')
- check_file_link('dest/1')
- check_file_link('dest/a/b/2')
- check_file_link('dest/a/b/3')
- check_file_link('dest/c/4')
- check_file_link('dest/c/d/5')
- check_file_link('dest/c/d/6')
- check_file_link('dest/c/d/e/7')
+ check_file_link('dest/1', 'source/1')
+ check_file_link('dest/a/b/2', 'source/a/b/2')
+ check_file_link('dest/a/b/3', 'source/a/b/3')
+ check_file_link('dest/c/4', 'source/c/4')
+ check_file_link('dest/c/d/5', 'source/c/d/5')
+ check_file_link('dest/c/d/6', 'source/c/d/6')
+ check_file_link('dest/c/d/e/7', 'source/c/d/e/7')
+
+ assert os.path.isabs(os.readlink('dest/1'))
+ assert os.path.isabs(os.readlink('dest/a/b/2'))
+ assert os.path.isabs(os.readlink('dest/a/b/3'))
+ assert os.path.isabs(os.readlink('dest/c/4'))
+ assert os.path.isabs(os.readlink('dest/c/d/5'))
+ assert os.path.isabs(os.readlink('dest/c/d/6'))
+ assert os.path.isabs(os.readlink('dest/c/d/e/7'))
+
+ link_tree.unmerge('dest')
+
+ assert not os.path.exists('dest')
+
+
+def test_merge_to_new_directory_relative(stage, link_tree):
+ with working_dir(stage.path):
+ link_tree.merge('dest', relative=True)
+
+ check_file_link('dest/1', 'source/1')
+ check_file_link('dest/a/b/2', 'source/a/b/2')
+ check_file_link('dest/a/b/3', 'source/a/b/3')
+ check_file_link('dest/c/4', 'source/c/4')
+ check_file_link('dest/c/d/5', 'source/c/d/5')
+ check_file_link('dest/c/d/6', 'source/c/d/6')
+ check_file_link('dest/c/d/e/7', 'source/c/d/e/7')
+
+ assert not os.path.isabs(os.readlink('dest/1'))
+ assert not os.path.isabs(os.readlink('dest/a/b/2'))
+ assert not os.path.isabs(os.readlink('dest/a/b/3'))
+ assert not os.path.isabs(os.readlink('dest/c/4'))
+ assert not os.path.isabs(os.readlink('dest/c/d/5'))
+ assert not os.path.isabs(os.readlink('dest/c/d/6'))
+ assert not os.path.isabs(os.readlink('dest/c/d/e/7'))
link_tree.unmerge('dest')
@@ -72,13 +107,13 @@ def test_merge_to_existing_directory(stage, link_tree):
link_tree.merge('dest')
- check_file_link('dest/1')
- check_file_link('dest/a/b/2')
- check_file_link('dest/a/b/3')
- check_file_link('dest/c/4')
- check_file_link('dest/c/d/5')
- check_file_link('dest/c/d/6')
- check_file_link('dest/c/d/e/7')
+ check_file_link('dest/1', 'source/1')
+ check_file_link('dest/a/b/2', 'source/a/b/2')
+ check_file_link('dest/a/b/3', 'source/a/b/3')
+ check_file_link('dest/c/4', 'source/c/4')
+ check_file_link('dest/c/d/5', 'source/c/d/5')
+ check_file_link('dest/c/d/6', 'source/c/d/6')
+ check_file_link('dest/c/d/e/7', 'source/c/d/e/7')
assert os.path.isfile('dest/x')
assert os.path.isfile('dest/a/b/y')