1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
##############################################################################
# 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=<predicate> -- Predicate indicating which files 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', lambda filename: False)
# 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(dest_dirpath):
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(src_relpath):
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)
|