blob: 0dc8f172d5c42b38d779cc11e896518bd6fccbd7 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
Patrick Williams92b42cb2022-09-03 06:53:57 -05002# Copyright OpenEmbedded Contributors
3#
Brad Bishopc342db32019-05-15 21:57:59 -04004# SPDX-License-Identifier: GPL-2.0-only
5#
6
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007import errno
8import glob
9import shutil
10import subprocess
11import os.path
12
13def join(*paths):
14 """Like os.path.join but doesn't treat absolute RHS specially"""
15 return os.path.normpath("/".join(paths))
16
17def relative(src, dest):
18 """ Return a relative path from src to dest.
19
20 >>> relative("/usr/bin", "/tmp/foo/bar")
21 ../../tmp/foo/bar
22
23 >>> relative("/usr/bin", "/usr/lib")
24 ../lib
25
26 >>> relative("/tmp", "/tmp/foo/bar")
27 foo/bar
28 """
29
30 return os.path.relpath(dest, src)
31
32def make_relative_symlink(path):
33 """ Convert an absolute symlink to a relative one """
34 if not os.path.islink(path):
35 return
36 link = os.readlink(path)
37 if not os.path.isabs(link):
38 return
39
40 # find the common ancestor directory
41 ancestor = path
42 depth = 0
43 while ancestor and not link.startswith(ancestor):
44 ancestor = ancestor.rpartition('/')[0]
45 depth += 1
46
47 if not ancestor:
48 print("make_relative_symlink() Error: unable to find the common ancestor of %s and its target" % path)
49 return
50
51 base = link.partition(ancestor)[2].strip('/')
52 while depth > 1:
53 base = "../" + base
54 depth -= 1
55
56 os.remove(path)
57 os.symlink(base, path)
58
Brad Bishop6e60e8b2018-02-01 10:27:11 -050059def replace_absolute_symlinks(basedir, d):
60 """
61 Walk basedir looking for absolute symlinks and replacing them with relative ones.
62 The absolute links are assumed to be relative to basedir
63 (compared to make_relative_symlink above which tries to compute common ancestors
64 using pattern matching instead)
65 """
66 for walkroot, dirs, files in os.walk(basedir):
67 for file in files + dirs:
68 path = os.path.join(walkroot, file)
69 if not os.path.islink(path):
70 continue
71 link = os.readlink(path)
72 if not os.path.isabs(link):
73 continue
74 walkdir = os.path.dirname(path.rpartition(basedir)[2])
75 base = os.path.relpath(link, walkdir)
76 bb.debug(2, "Replacing absolute path %s with relative path %s" % (link, base))
77 os.remove(path)
78 os.symlink(base, path)
79
Patrick Williamsc124f4f2015-09-15 14:41:29 -050080def format_display(path, metadata):
81 """ Prepare a path for display to the user. """
Brad Bishop6e60e8b2018-02-01 10:27:11 -050082 rel = relative(metadata.getVar("TOPDIR"), path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050083 if len(rel) > len(path):
84 return path
85 else:
86 return rel
87
88def copytree(src, dst):
89 # We could use something like shutil.copytree here but it turns out to
90 # to be slow. It takes twice as long copying to an empty directory.
91 # If dst already has contents performance can be 15 time slower
92 # This way we also preserve hardlinks between files in the tree.
93
94 bb.utils.mkdirhier(dst)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080095 cmd = "tar --xattrs --xattrs-include='*' -cf - -S -C %s -p . | tar --xattrs --xattrs-include='*' -xf - -C %s" % (src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060096 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050097
98def copyhardlinktree(src, dst):
Brad Bishopc342db32019-05-15 21:57:59 -040099 """Make a tree of hard links when possible, otherwise copy."""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100 bb.utils.mkdirhier(dst)
101 if os.path.isdir(src) and not len(os.listdir(src)):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600102 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500103
Andrew Geissler82c905d2020-04-13 13:39:40 -0500104 canhard = False
105 testfile = None
106 for root, dirs, files in os.walk(src):
107 if len(files):
108 testfile = os.path.join(root, files[0])
109 break
110
111 if testfile is not None:
112 try:
113 os.link(testfile, os.path.join(dst, 'testfile'))
114 os.unlink(os.path.join(dst, 'testfile'))
115 canhard = True
116 except Exception as e:
117 bb.debug(2, "Hardlink test failed with " + str(e))
118
119 if (canhard):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120 # Need to copy directories only with tar first since cp will error if two
121 # writers try and create a directory at the same time
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800122 cmd = "cd %s; find . -type d -print | tar --xattrs --xattrs-include='*' -cf - -S -C %s -p --no-recursion --files-from - | tar --xattrs --xattrs-include='*' -xhf - -C %s" % (src, src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600123 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
124 source = ''
125 if os.path.isdir(src):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600126 if len(glob.glob('%s/.??*' % src)) > 0:
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500127 source = './.??* '
128 source += './*'
129 s_dir = src
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600130 else:
131 source = src
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500132 s_dir = os.getcwd()
133 cmd = 'cp -afl --preserve=xattr %s %s' % (source, os.path.realpath(dst))
134 subprocess.check_output(cmd, shell=True, cwd=s_dir, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500135 else:
136 copytree(src, dst)
137
Brad Bishopc342db32019-05-15 21:57:59 -0400138def copyhardlink(src, dst):
139 """Make a hard link when possible, otherwise copy."""
140
Andrew Geissler82c905d2020-04-13 13:39:40 -0500141 try:
Brad Bishopc342db32019-05-15 21:57:59 -0400142 os.link(src, dst)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500143 except OSError:
Brad Bishopc342db32019-05-15 21:57:59 -0400144 shutil.copy(src, dst)
145
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500146def remove(path, recurse=True):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500147 """
148 Equivalent to rm -f or rm -rf
149 NOTE: be careful about passing paths that may contain filenames with
150 wildcards in them (as opposed to passing an actual wildcarded path) -
151 since we use glob.glob() to expand the path. Filenames containing
152 square brackets are particularly problematic since the they may not
153 actually expand to match the original filename.
154 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 for name in glob.glob(path):
156 try:
157 os.unlink(name)
158 except OSError as exc:
159 if recurse and exc.errno == errno.EISDIR:
160 shutil.rmtree(name)
161 elif exc.errno != errno.ENOENT:
162 raise
163
164def symlink(source, destination, force=False):
165 """Create a symbolic link"""
166 try:
167 if force:
168 remove(destination)
169 os.symlink(source, destination)
170 except OSError as e:
171 if e.errno != errno.EEXIST or os.readlink(destination) != source:
172 raise
173
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500174def find(dir, **walkoptions):
175 """ Given a directory, recurses into that directory,
176 returning all files as absolute paths. """
177
178 for root, dirs, files in os.walk(dir, **walkoptions):
179 for file in files:
180 yield os.path.join(root, file)
181
182
183## realpath() related functions
184def __is_path_below(file, root):
185 return (file + os.path.sep).startswith(root)
186
187def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
188 """Calculates real path of symlink 'start' + 'rel_path' below
189 'root'; no part of 'start' below 'root' must contain symlinks. """
190 have_dir = True
191
192 for d in rel_path.split(os.path.sep):
193 if not have_dir and not assume_dir:
194 raise OSError(errno.ENOENT, "no such directory %s" % start)
195
196 if d == os.path.pardir: # '..'
197 if len(start) >= len(root):
198 # do not follow '..' before root
199 start = os.path.dirname(start)
200 else:
201 # emit warning?
202 pass
203 else:
204 (start, have_dir) = __realpath(os.path.join(start, d),
205 root, loop_cnt, assume_dir)
206
207 assert(__is_path_below(start, root))
208
209 return start
210
211def __realpath(file, root, loop_cnt, assume_dir):
212 while os.path.islink(file) and len(file) >= len(root):
213 if loop_cnt == 0:
214 raise OSError(errno.ELOOP, file)
215
216 loop_cnt -= 1
217 target = os.path.normpath(os.readlink(file))
218
219 if not os.path.isabs(target):
220 tdir = os.path.dirname(file)
221 assert(__is_path_below(tdir, root))
222 else:
223 tdir = root
224
225 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
226
227 try:
228 is_dir = os.path.isdir(file)
229 except:
230 is_dir = false
231
232 return (file, is_dir)
233
234def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
235 """ Returns the canonical path of 'file' with assuming a
236 toplevel 'root' directory. When 'use_physdir' is set, all
237 preceding path components of 'file' will be resolved first;
238 this flag should be set unless it is guaranteed that there is
239 no symlink in the path. When 'assume_dir' is not set, missing
240 path components will raise an ENOENT error"""
241
242 root = os.path.normpath(root)
243 file = os.path.normpath(file)
244
245 if not root.endswith(os.path.sep):
246 # letting root end with '/' makes some things easier
247 root = root + os.path.sep
248
249 if not __is_path_below(file, root):
250 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
251
252 try:
253 if use_physdir:
254 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
255 else:
256 file = __realpath(file, root, loop_cnt, assume_dir)[0]
257 except OSError as e:
258 if e.errno == errno.ELOOP:
259 # make ELOOP more readable; without catching it, there will
260 # be printed a backtrace with 100s of OSError exceptions
261 # else
262 raise OSError(errno.ELOOP,
263 "too much recursions while resolving '%s'; loop in '%s'" %
264 (file, e.strerror))
265
266 raise
267
268 return file
Brad Bishop316dfdd2018-06-25 12:45:53 -0400269
270def is_path_parent(possible_parent, *paths):
271 """
272 Return True if a path is the parent of another, False otherwise.
273 Multiple paths to test can be specified in which case all
274 specified test paths must be under the parent in order to
275 return True.
276 """
277 def abs_path_trailing(pth):
278 pth_abs = os.path.abspath(pth)
279 if not pth_abs.endswith(os.sep):
280 pth_abs += os.sep
281 return pth_abs
282
283 possible_parent_abs = abs_path_trailing(possible_parent)
284 if not paths:
285 return False
286 for path in paths:
287 path_abs = abs_path_trailing(path)
288 if not path_abs.startswith(possible_parent_abs):
289 return False
290 return True
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800291
292def which_wild(pathname, path=None, mode=os.F_OK, *, reverse=False, candidates=False):
293 """Search a search path for pathname, supporting wildcards.
294
295 Return all paths in the specific search path matching the wildcard pattern
296 in pathname, returning only the first encountered for each file. If
297 candidates is True, information on all potential candidate paths are
298 included.
299 """
300 paths = (path or os.environ.get('PATH', os.defpath)).split(':')
301 if reverse:
302 paths.reverse()
303
304 seen, files = set(), []
305 for index, element in enumerate(paths):
306 if not os.path.isabs(element):
307 element = os.path.abspath(element)
308
309 candidate = os.path.join(element, pathname)
310 globbed = glob.glob(candidate)
311 if globbed:
312 for found_path in sorted(globbed):
313 if not os.access(found_path, mode):
314 continue
315 rel = os.path.relpath(found_path, element)
316 if rel not in seen:
317 seen.add(rel)
318 if candidates:
319 files.append((found_path, [os.path.join(p, rel) for p in paths[:index+1]]))
320 else:
321 files.append(found_path)
322
323 return files
324
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600325def canonicalize(paths, sep=','):
326 """Given a string with paths (separated by commas by default), expand
327 each path using os.path.realpath() and return the resulting paths as a
328 string (separated using the same separator a the original string).
329 """
330 # Ignore paths containing "$" as they are assumed to be unexpanded bitbake
331 # variables. Normally they would be ignored, e.g., when passing the paths
332 # through the shell they would expand to empty strings. However, when they
333 # are passed through os.path.realpath(), it will cause them to be prefixed
334 # with the absolute path to the current directory and thus not be empty
335 # anymore.
336 #
337 # Also maintain trailing slashes, as the paths may actually be used as
338 # prefixes in sting compares later on, where the slashes then are important.
339 canonical_paths = []
340 for path in (paths or '').split(sep):
341 if '$' not in path:
342 trailing_slash = path.endswith('/') and '/' or ''
343 canonical_paths.append(os.path.realpath(path) + trailing_slash)
344
345 return sep.join(canonical_paths)