summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe/path.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oe/path.py')
-rw-r--r--meta/lib/oe/path.py261
1 files changed, 261 insertions, 0 deletions
diff --git a/meta/lib/oe/path.py b/meta/lib/oe/path.py
new file mode 100644
index 0000000000..1310e38fe1
--- /dev/null
+++ b/meta/lib/oe/path.py
@@ -0,0 +1,261 @@
1import errno
2import glob
3import shutil
4import subprocess
5import os.path
6
7def join(*paths):
8 """Like os.path.join but doesn't treat absolute RHS specially"""
9 return os.path.normpath("/".join(paths))
10
11def relative(src, dest):
12 """ Return a relative path from src to dest.
13
14 >>> relative("/usr/bin", "/tmp/foo/bar")
15 ../../tmp/foo/bar
16
17 >>> relative("/usr/bin", "/usr/lib")
18 ../lib
19
20 >>> relative("/tmp", "/tmp/foo/bar")
21 foo/bar
22 """
23
24 if hasattr(os.path, "relpath"):
25 return os.path.relpath(dest, src)
26 else:
27 destlist = os.path.normpath(dest).split(os.path.sep)
28 srclist = os.path.normpath(src).split(os.path.sep)
29
30 # Find common section of the path
31 common = os.path.commonprefix([destlist, srclist])
32 commonlen = len(common)
33
34 # Climb back to the point where they differentiate
35 relpath = [ os.path.pardir ] * (len(srclist) - commonlen)
36 if commonlen < len(destlist):
37 # Add remaining portion
38 relpath += destlist[commonlen:]
39
40 return os.path.sep.join(relpath)
41
42def make_relative_symlink(path):
43 """ Convert an absolute symlink to a relative one """
44 if not os.path.islink(path):
45 return
46 link = os.readlink(path)
47 if not os.path.isabs(link):
48 return
49
50 # find the common ancestor directory
51 ancestor = path
52 depth = 0
53 while ancestor and not link.startswith(ancestor):
54 ancestor = ancestor.rpartition('/')[0]
55 depth += 1
56
57 if not ancestor:
58 print("make_relative_symlink() Error: unable to find the common ancestor of %s and its target" % path)
59 return
60
61 base = link.partition(ancestor)[2].strip('/')
62 while depth > 1:
63 base = "../" + base
64 depth -= 1
65
66 os.remove(path)
67 os.symlink(base, path)
68
69def format_display(path, metadata):
70 """ Prepare a path for display to the user. """
71 rel = relative(metadata.getVar("TOPDIR", True), path)
72 if len(rel) > len(path):
73 return path
74 else:
75 return rel
76
77def copytree(src, dst):
78 # We could use something like shutil.copytree here but it turns out to
79 # to be slow. It takes twice as long copying to an empty directory.
80 # If dst already has contents performance can be 15 time slower
81 # This way we also preserve hardlinks between files in the tree.
82
83 bb.utils.mkdirhier(dst)
84 cmd = 'tar -cf - -C %s -p . | tar -xf - -C %s' % (src, dst)
85 check_output(cmd, shell=True, stderr=subprocess.STDOUT)
86
87def copyhardlinktree(src, dst):
88 """ Make the hard link when possible, otherwise copy. """
89 bb.utils.mkdirhier(dst)
90 if os.path.isdir(src) and not len(os.listdir(src)):
91 return
92
93 if (os.stat(src).st_dev == os.stat(dst).st_dev):
94 # Need to copy directories only with tar first since cp will error if two
95 # writers try and create a directory at the same time
96 cmd = 'cd %s; find . -type d -print | tar -cf - -C %s -p --files-from - | tar -xf - -C %s' % (src, src, dst)
97 check_output(cmd, shell=True, stderr=subprocess.STDOUT)
98 if os.path.isdir(src):
99 src = src + "/*"
100 cmd = 'cp -afl %s %s' % (src, dst)
101 check_output(cmd, shell=True, stderr=subprocess.STDOUT)
102 else:
103 copytree(src, dst)
104
105def remove(path, recurse=True):
106 """Equivalent to rm -f or rm -rf"""
107 for name in glob.glob(path):
108 try:
109 os.unlink(name)
110 except OSError as exc:
111 if recurse and exc.errno == errno.EISDIR:
112 shutil.rmtree(name)
113 elif exc.errno != errno.ENOENT:
114 raise
115
116def symlink(source, destination, force=False):
117 """Create a symbolic link"""
118 try:
119 if force:
120 remove(destination)
121 os.symlink(source, destination)
122 except OSError as e:
123 if e.errno != errno.EEXIST or os.readlink(destination) != source:
124 raise
125
126class CalledProcessError(Exception):
127 def __init__(self, retcode, cmd, output = None):
128 self.retcode = retcode
129 self.cmd = cmd
130 self.output = output
131 def __str__(self):
132 return "Command '%s' returned non-zero exit status %d with output %s" % (self.cmd, self.retcode, self.output)
133
134# Not needed when we move to python 2.7
135def check_output(*popenargs, **kwargs):
136 r"""Run command with arguments and return its output as a byte string.
137
138 If the exit code was non-zero it raises a CalledProcessError. The
139 CalledProcessError object will have the return code in the returncode
140 attribute and output in the output attribute.
141
142 The arguments are the same as for the Popen constructor. Example:
143
144 >>> check_output(["ls", "-l", "/dev/null"])
145 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
146
147 The stdout argument is not allowed as it is used internally.
148 To capture standard error in the result, use stderr=STDOUT.
149
150 >>> check_output(["/bin/sh", "-c",
151 ... "ls -l non_existent_file ; exit 0"],
152 ... stderr=STDOUT)
153 'ls: non_existent_file: No such file or directory\n'
154 """
155 if 'stdout' in kwargs:
156 raise ValueError('stdout argument not allowed, it will be overridden.')
157 process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
158 output, unused_err = process.communicate()
159 retcode = process.poll()
160 if retcode:
161 cmd = kwargs.get("args")
162 if cmd is None:
163 cmd = popenargs[0]
164 raise CalledProcessError(retcode, cmd, output=output)
165 return output
166
167def find(dir, **walkoptions):
168 """ Given a directory, recurses into that directory,
169 returning all files as absolute paths. """
170
171 for root, dirs, files in os.walk(dir, **walkoptions):
172 for file in files:
173 yield os.path.join(root, file)
174
175
176## realpath() related functions
177def __is_path_below(file, root):
178 return (file + os.path.sep).startswith(root)
179
180def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
181 """Calculates real path of symlink 'start' + 'rel_path' below
182 'root'; no part of 'start' below 'root' must contain symlinks. """
183 have_dir = True
184
185 for d in rel_path.split(os.path.sep):
186 if not have_dir and not assume_dir:
187 raise OSError(errno.ENOENT, "no such directory %s" % start)
188
189 if d == os.path.pardir: # '..'
190 if len(start) >= len(root):
191 # do not follow '..' before root
192 start = os.path.dirname(start)
193 else:
194 # emit warning?
195 pass
196 else:
197 (start, have_dir) = __realpath(os.path.join(start, d),
198 root, loop_cnt, assume_dir)
199
200 assert(__is_path_below(start, root))
201
202 return start
203
204def __realpath(file, root, loop_cnt, assume_dir):
205 while os.path.islink(file) and len(file) >= len(root):
206 if loop_cnt == 0:
207 raise OSError(errno.ELOOP, file)
208
209 loop_cnt -= 1
210 target = os.path.normpath(os.readlink(file))
211
212 if not os.path.isabs(target):
213 tdir = os.path.dirname(file)
214 assert(__is_path_below(tdir, root))
215 else:
216 tdir = root
217
218 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
219
220 try:
221 is_dir = os.path.isdir(file)
222 except:
223 is_dir = false
224
225 return (file, is_dir)
226
227def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
228 """ Returns the canonical path of 'file' with assuming a
229 toplevel 'root' directory. When 'use_physdir' is set, all
230 preceding path components of 'file' will be resolved first;
231 this flag should be set unless it is guaranteed that there is
232 no symlink in the path. When 'assume_dir' is not set, missing
233 path components will raise an ENOENT error"""
234
235 root = os.path.normpath(root)
236 file = os.path.normpath(file)
237
238 if not root.endswith(os.path.sep):
239 # letting root end with '/' makes some things easier
240 root = root + os.path.sep
241
242 if not __is_path_below(file, root):
243 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
244
245 try:
246 if use_physdir:
247 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
248 else:
249 file = __realpath(file, root, loop_cnt, assume_dir)[0]
250 except OSError as e:
251 if e.errno == errno.ELOOP:
252 # make ELOOP more readable; without catching it, there will
253 # be printed a backtrace with 100s of OSError exceptions
254 # else
255 raise OSError(errno.ELOOP,
256 "too much recursions while resolving '%s'; loop in '%s'" %
257 (file, e.strerror))
258
259 raise
260
261 return file