summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2015-01-08 22:46:31 -0800
committerTodd Gamblin <tgamblin@llnl.gov>2015-02-02 11:19:52 -0800
commit82946d29147bbe63855f94b9c2ebd4a21cd0a3d6 (patch)
tree7f2943f2dff535723015a29e6cc2b1ca3f415660
parent99775434785779d223997a9e41972da470214e5d (diff)
downloadspack-82946d29147bbe63855f94b9c2ebd4a21cd0a3d6.tar.gz
spack-82946d29147bbe63855f94b9c2ebd4a21cd0a3d6.tar.bz2
spack-82946d29147bbe63855f94b9c2ebd4a21cd0a3d6.tar.xz
spack-82946d29147bbe63855f94b9c2ebd4a21cd0a3d6.zip
Move symlink tree routines to LinkTree class.
-rw-r--r--lib/spack/llnl/util/filesystem.py82
-rw-r--r--lib/spack/llnl/util/link_tree.py168
-rw-r--r--lib/spack/spack/package.py16
3 files changed, 176 insertions, 90 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index 9fb76d3a35..0578415653 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -24,8 +24,7 @@
##############################################################################
__all__ = ['set_install_permissions', 'install', 'expand_user', 'working_dir',
'touch', 'mkdirp', 'force_remove', 'join_path', 'ancestor',
- 'can_access', 'filter_file', 'change_sed_delimiter', 'is_exe',
- 'check_link_tree', 'merge_link_tree', 'unmerge_link_tree']
+ 'can_access', 'filter_file', 'change_sed_delimiter', 'is_exe']
import os
import sys
@@ -223,82 +222,3 @@ def ancestor(dir, n=1):
def can_access(file_name):
"""True if we have read/write access to the file."""
return os.access(file_name, os.R_OK|os.W_OK)
-
-
-def traverse_link_tree(src_root, dest_root, follow_nonexisting=True, **kwargs):
- # Yield directories before or after their contents.
- order = kwargs.get('order', 'pre')
- if order not in ('pre', 'post'):
- raise ValueError("Order must be 'pre' or 'post'.")
-
- # List of relative paths to ignore under the src root.
- ignore = kwargs.get('ignore', None)
- if isinstance(ignore, basestring):
- ignore = (ignore,)
-
- for dirpath, dirnames, filenames in os.walk(src_root):
- rel_path = dirpath[len(src_root):]
- rel_path = rel_path.lstrip(os.path.sep)
- dest_dirpath = os.path.join(dest_root, rel_path)
-
- # Don't descend into ignored directories
- if ignore and dest_dirpath in ignore:
- return
-
- # Don't descend into dirs in dest that do not exist in src.
- if not follow_nonexisting:
- dirnames[:] = [
- d for d in dirnames
- if os.path.exists(os.path.join(dest_dirpath, d))]
-
- # preorder yields directories before children
- if order == 'pre':
- yield (dirpath, dest_dirpath)
-
- for name in filenames:
- src_file = os.path.join(dirpath, name)
- dest_file = os.path.join(dest_dirpath, name)
-
- # Ignore particular paths inside the install root.
- src_relpath = src_file[len(src_root):]
- src_relpath = src_relpath.lstrip(os.path.sep)
- if ignore and src_relpath in ignore:
- continue
-
- yield (src_file, dest_file)
-
- # postorder yields directories after children
- if order == 'post':
- yield (dirpath, dest_dirpath)
-
-
-
-def check_link_tree(src_root, dest_root, **kwargs):
- for src, dest in traverse_link_tree(src_root, dest_root, False, **kwargs):
- if os.path.exists(dest) and not os.path.isdir(dest):
- return dest
- return None
-
-
-def merge_link_tree(src_root, dest_root, **kwargs):
- kwargs['order'] = 'pre'
- for src, dest in traverse_link_tree(src_root, dest_root, **kwargs):
- if os.path.isdir(src):
- mkdirp(dest)
- else:
- assert(not os.path.exists(dest))
- os.symlink(src, dest)
-
-
-def unmerge_link_tree(src_root, dest_root, **kwargs):
- kwargs['order'] = 'post'
- for src, dest in traverse_link_tree(src_root, dest_root, **kwargs):
- if os.path.isdir(dest):
- if not os.listdir(dest):
- # TODO: what if empty directories were present pre-merge?
- shutil.rmtree(dest, ignore_errors=True)
-
- elif os.path.exists(dest):
- if not os.path.islink(dest):
- raise ValueError("%s is not a link tree!" % dest)
- os.remove(dest)
diff --git a/lib/spack/llnl/util/link_tree.py b/lib/spack/llnl/util/link_tree.py
new file mode 100644
index 0000000000..19c2d46938
--- /dev/null
+++ b/lib/spack/llnl/util/link_tree.py
@@ -0,0 +1,168 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""LinkTree class for setting up trees of symbolic links."""
+__all__ = ['LinkTree']
+
+import os
+import shutil
+from llnl.util.filesystem import mkdirp
+
+
+class LinkTree(object):
+ """Class to create trees of symbolic links from a source directory.
+
+ LinkTree objects are constructed with a source root. Their
+ methods allow you to create and delete trees of symbolic links
+ back to the source tree in specific destination directories.
+ Trees comprise symlinks only to files; directries are never
+ symlinked to, to prevent the source directory from ever being
+ modified.
+
+ """
+ def __init__(self, source_root):
+ self._root = source_root
+
+
+ def traverse(self, dest_root, **kwargs):
+ """Traverse LinkTree root and dest simultaneously.
+
+ Walks the LinkTree directory in pre or post order. Yields
+ each file in the source directory with a matching path from
+ the dest directory. e.g., for this tree::
+
+ root/
+ a/
+ file1
+ file2
+ b/
+ file3
+
+ When called on dest, this yields::
+
+ ('root', 'dest')
+ ('root/a', 'dest/a')
+ ('root/a/file1', 'dest/a/file1')
+ ('root/a/file2', 'dest/a/file2')
+ ('root/b', 'dest/b')
+ ('root/b/file3', 'dest/b/file3')
+
+ Optional args:
+
+ order=[pre|post] -- Whether to do pre- or post-order traveral.
+
+ ignore=<container> -- Optional container of root-relative
+ paths to ignore.
+
+ follow_nonexisting -- Whether to descend into directories in
+ src that do not exit in dest.
+
+ """
+ # Yield directories before or after their contents.
+ order = kwargs.get('order', 'pre')
+ if order not in ('pre', 'post'):
+ raise ValueError("Order must be 'pre' or 'post'.")
+
+ # List of relative paths to ignore under the src root.
+ ignore = kwargs.get('ignore', None)
+ if isinstance(ignore, basestring):
+ ignore = (ignore,)
+
+ # Whether to descend when dirs dont' exist in dest.
+ follow_nonexisting = kwargs.get('follow_nonexisting', True)
+
+ for dirpath, dirnames, filenames in os.walk(self._root):
+ rel_path = dirpath[len(self._root):]
+ rel_path = rel_path.lstrip(os.path.sep)
+ dest_dirpath = os.path.join(dest_root, rel_path)
+
+ # Don't descend into ignored directories
+ if ignore and dest_dirpath in ignore:
+ return
+
+ # Don't descend into dirs in dest that do not exist in src.
+ if not follow_nonexisting:
+ dirnames[:] = [
+ d for d in dirnames
+ if os.path.exists(os.path.join(dest_dirpath, d))]
+
+ # preorder yields directories before children
+ if order == 'pre':
+ yield (dirpath, dest_dirpath)
+
+ for name in filenames:
+ src_file = os.path.join(dirpath, name)
+ dest_file = os.path.join(dest_dirpath, name)
+
+ # Ignore particular paths inside the install root.
+ src_relpath = src_file[len(self._root):]
+ src_relpath = src_relpath.lstrip(os.path.sep)
+ if ignore and src_relpath in ignore:
+ continue
+
+ yield (src_file, dest_file)
+
+ # postorder yields directories after children
+ if order == 'post':
+ yield (dirpath, dest_dirpath)
+
+
+
+ def find_conflict(self, dest_root, **kwargs):
+ """Returns the first file in dest that also exists in src."""
+ kwargs['follow_nonexisting'] = False
+ for src, dest in self.traverse(dest_root, **kwargs):
+ if os.path.exists(dest) and not os.path.isdir(dest):
+ return dest
+ return None
+
+
+ def merge(self, dest_root, **kwargs):
+ """Link all files in src into dest, creating directories if necessary."""
+ kwargs['order'] = 'pre'
+ for src, dest in self.traverse(dest_root, **kwargs):
+ if os.path.isdir(src):
+ mkdirp(dest)
+ else:
+ assert(not os.path.exists(dest))
+ os.symlink(src, dest)
+
+
+ def unmerge(self, dest_root, **kwargs):
+ """Unlink all files in dest that exist in src.
+
+ Unlinks directories in dest if they are empty.
+
+ """
+ kwargs['order'] = 'post'
+ for src, dest in self.traverse(dest_root, **kwargs):
+ if os.path.isdir(dest):
+ if not os.listdir(dest):
+ # TODO: what if empty directories were present pre-merge?
+ shutil.rmtree(dest, ignore_errors=True)
+
+ elif os.path.exists(dest):
+ if not os.path.islink(dest):
+ raise ValueError("%s is not a link tree!" % dest)
+ os.remove(dest)
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index b7dae552e4..da251dc4e8 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -45,6 +45,7 @@ import textwrap
from StringIO import StringIO
import llnl.util.tty as tty
+from llnl.util.link_tree import LinkTree
from llnl.util.filesystem import *
from llnl.util.lang import *
@@ -918,15 +919,12 @@ class Package(object):
always executed.
"""
- conflict = check_link_tree(
- extension.prefix, self.prefix,
- ignore=spack.install_layout.hidden_file_paths)
-
+ tree = LinkTree(extension.prefix)
+ conflict = tree.find_conflict(
+ self.prefix, ignore=spack.install_layout.hidden_file_paths)
if conflict:
raise ExtensionConflictError(conflict)
-
- merge_link_tree(extension.prefix, self.prefix,
- ignore=spack.install_layout.hidden_file_paths)
+ tree.merge(self.prefix, ignore=spack.install_layout.hidden_file_paths)
def do_deactivate(self, extension):
@@ -950,8 +948,8 @@ class Package(object):
always executed.
"""
- unmerge_link_tree(extension.prefix, self.prefix,
- ignore=spack.install_layout.hidden_file_paths)
+ tree = LinkTree(extension.prefix)
+ tree.unmerge(self.prefix, ignore=spack.install_layout.hidden_file_paths)
tty.msg("Deactivated %s as extension of %s."
% (extension.spec.short_spec, self.spec.short_spec))