blob: 5d21cdcbdff169d8b325a786e3d3ad14d3f16c9f [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 = './.??* '
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600128 if len(glob.glob('%s/**' % src)) > 0:
129 source += './*'
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500130 s_dir = src
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600131 else:
132 source = src
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500133 s_dir = os.getcwd()
134 cmd = 'cp -afl --preserve=xattr %s %s' % (source, os.path.realpath(dst))
135 subprocess.check_output(cmd, shell=True, cwd=s_dir, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500136 else:
137 copytree(src, dst)
138
Brad Bishopc342db32019-05-15 21:57:59 -0400139def copyhardlink(src, dst):
140 """Make a hard link when possible, otherwise copy."""
141
Andrew Geissler82c905d2020-04-13 13:39:40 -0500142 try:
Brad Bishopc342db32019-05-15 21:57:59 -0400143 os.link(src, dst)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500144 except OSError:
Brad Bishopc342db32019-05-15 21:57:59 -0400145 shutil.copy(src, dst)
146
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500147def remove(path, recurse=True):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500148 """
149 Equivalent to rm -f or rm -rf
150 NOTE: be careful about passing paths that may contain filenames with
151 wildcards in them (as opposed to passing an actual wildcarded path) -
152 since we use glob.glob() to expand the path. Filenames containing
153 square brackets are particularly problematic since the they may not
154 actually expand to match the original filename.
155 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500156 for name in glob.glob(path):
157 try:
158 os.unlink(name)
159 except OSError as exc:
160 if recurse and exc.errno == errno.EISDIR:
161 shutil.rmtree(name)
162 elif exc.errno != errno.ENOENT:
163 raise
164
165def symlink(source, destination, force=False):
166 """Create a symbolic link"""
167 try:
168 if force:
169 remove(destination)
170 os.symlink(source, destination)
171 except OSError as e:
172 if e.errno != errno.EEXIST or os.readlink(destination) != source:
173 raise
174
Patrick Williams169d7bc2024-01-05 11:33:25 -0600175def relsymlink(target, name, force=False):
176 symlink(os.path.relpath(target, os.path.dirname(name)), name, force=force)
177
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178def find(dir, **walkoptions):
179 """ Given a directory, recurses into that directory,
180 returning all files as absolute paths. """
181
182 for root, dirs, files in os.walk(dir, **walkoptions):
183 for file in files:
184 yield os.path.join(root, file)
185
186
187## realpath() related functions
188def __is_path_below(file, root):
189 return (file + os.path.sep).startswith(root)
190
191def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
192 """Calculates real path of symlink 'start' + 'rel_path' below
193 'root'; no part of 'start' below 'root' must contain symlinks. """
194 have_dir = True
195
196 for d in rel_path.split(os.path.sep):
197 if not have_dir and not assume_dir:
198 raise OSError(errno.ENOENT, "no such directory %s" % start)
199
200 if d == os.path.pardir: # '..'
201 if len(start) >= len(root):
202 # do not follow '..' before root
203 start = os.path.dirname(start)
204 else:
205 # emit warning?
206 pass
207 else:
208 (start, have_dir) = __realpath(os.path.join(start, d),
209 root, loop_cnt, assume_dir)
210
211 assert(__is_path_below(start, root))
212
213 return start
214
215def __realpath(file, root, loop_cnt, assume_dir):
216 while os.path.islink(file) and len(file) >= len(root):
217 if loop_cnt == 0:
218 raise OSError(errno.ELOOP, file)
219
220 loop_cnt -= 1
221 target = os.path.normpath(os.readlink(file))
222
223 if not os.path.isabs(target):
224 tdir = os.path.dirname(file)
225 assert(__is_path_below(tdir, root))
226 else:
227 tdir = root
228
229 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
230
231 try:
232 is_dir = os.path.isdir(file)
233 except:
234 is_dir = false
235
236 return (file, is_dir)
237
238def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
239 """ Returns the canonical path of 'file' with assuming a
240 toplevel 'root' directory. When 'use_physdir' is set, all
241 preceding path components of 'file' will be resolved first;
242 this flag should be set unless it is guaranteed that there is
243 no symlink in the path. When 'assume_dir' is not set, missing
244 path components will raise an ENOENT error"""
245
246 root = os.path.normpath(root)
247 file = os.path.normpath(file)
248
249 if not root.endswith(os.path.sep):
250 # letting root end with '/' makes some things easier
251 root = root + os.path.sep
252
253 if not __is_path_below(file, root):
254 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
255
256 try:
257 if use_physdir:
258 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
259 else:
260 file = __realpath(file, root, loop_cnt, assume_dir)[0]
261 except OSError as e:
262 if e.errno == errno.ELOOP:
263 # make ELOOP more readable; without catching it, there will
264 # be printed a backtrace with 100s of OSError exceptions
265 # else
266 raise OSError(errno.ELOOP,
267 "too much recursions while resolving '%s'; loop in '%s'" %
268 (file, e.strerror))
269
270 raise
271
272 return file
Brad Bishop316dfdd2018-06-25 12:45:53 -0400273
274def is_path_parent(possible_parent, *paths):
275 """
276 Return True if a path is the parent of another, False otherwise.
277 Multiple paths to test can be specified in which case all
278 specified test paths must be under the parent in order to
279 return True.
280 """
281 def abs_path_trailing(pth):
282 pth_abs = os.path.abspath(pth)
283 if not pth_abs.endswith(os.sep):
284 pth_abs += os.sep
285 return pth_abs
286
287 possible_parent_abs = abs_path_trailing(possible_parent)
288 if not paths:
289 return False
290 for path in paths:
291 path_abs = abs_path_trailing(path)
292 if not path_abs.startswith(possible_parent_abs):
293 return False
294 return True
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800295
296def which_wild(pathname, path=None, mode=os.F_OK, *, reverse=False, candidates=False):
297 """Search a search path for pathname, supporting wildcards.
298
299 Return all paths in the specific search path matching the wildcard pattern
300 in pathname, returning only the first encountered for each file. If
301 candidates is True, information on all potential candidate paths are
302 included.
303 """
304 paths = (path or os.environ.get('PATH', os.defpath)).split(':')
305 if reverse:
306 paths.reverse()
307
308 seen, files = set(), []
309 for index, element in enumerate(paths):
310 if not os.path.isabs(element):
311 element = os.path.abspath(element)
312
313 candidate = os.path.join(element, pathname)
314 globbed = glob.glob(candidate)
315 if globbed:
316 for found_path in sorted(globbed):
317 if not os.access(found_path, mode):
318 continue
319 rel = os.path.relpath(found_path, element)
320 if rel not in seen:
321 seen.add(rel)
322 if candidates:
323 files.append((found_path, [os.path.join(p, rel) for p in paths[:index+1]]))
324 else:
325 files.append(found_path)
326
327 return files
328
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600329def canonicalize(paths, sep=','):
330 """Given a string with paths (separated by commas by default), expand
331 each path using os.path.realpath() and return the resulting paths as a
332 string (separated using the same separator a the original string).
333 """
334 # Ignore paths containing "$" as they are assumed to be unexpanded bitbake
335 # variables. Normally they would be ignored, e.g., when passing the paths
336 # through the shell they would expand to empty strings. However, when they
337 # are passed through os.path.realpath(), it will cause them to be prefixed
338 # with the absolute path to the current directory and thus not be empty
339 # anymore.
340 #
341 # Also maintain trailing slashes, as the paths may actually be used as
342 # prefixes in sting compares later on, where the slashes then are important.
343 canonical_paths = []
344 for path in (paths or '').split(sep):
345 if '$' not in path:
346 trailing_slash = path.endswith('/') and '/' or ''
347 canonical_paths.append(os.path.realpath(path) + trailing_slash)
348
349 return sep.join(canonical_paths)