blob: e2f1913a35ce50695e055676d1a785195ca8a92e [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 Williamsc124f4f2015-09-15 14:41:29 -0500175def find(dir, **walkoptions):
176 """ Given a directory, recurses into that directory,
177 returning all files as absolute paths. """
178
179 for root, dirs, files in os.walk(dir, **walkoptions):
180 for file in files:
181 yield os.path.join(root, file)
182
183
184## realpath() related functions
185def __is_path_below(file, root):
186 return (file + os.path.sep).startswith(root)
187
188def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
189 """Calculates real path of symlink 'start' + 'rel_path' below
190 'root'; no part of 'start' below 'root' must contain symlinks. """
191 have_dir = True
192
193 for d in rel_path.split(os.path.sep):
194 if not have_dir and not assume_dir:
195 raise OSError(errno.ENOENT, "no such directory %s" % start)
196
197 if d == os.path.pardir: # '..'
198 if len(start) >= len(root):
199 # do not follow '..' before root
200 start = os.path.dirname(start)
201 else:
202 # emit warning?
203 pass
204 else:
205 (start, have_dir) = __realpath(os.path.join(start, d),
206 root, loop_cnt, assume_dir)
207
208 assert(__is_path_below(start, root))
209
210 return start
211
212def __realpath(file, root, loop_cnt, assume_dir):
213 while os.path.islink(file) and len(file) >= len(root):
214 if loop_cnt == 0:
215 raise OSError(errno.ELOOP, file)
216
217 loop_cnt -= 1
218 target = os.path.normpath(os.readlink(file))
219
220 if not os.path.isabs(target):
221 tdir = os.path.dirname(file)
222 assert(__is_path_below(tdir, root))
223 else:
224 tdir = root
225
226 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
227
228 try:
229 is_dir = os.path.isdir(file)
230 except:
231 is_dir = false
232
233 return (file, is_dir)
234
235def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
236 """ Returns the canonical path of 'file' with assuming a
237 toplevel 'root' directory. When 'use_physdir' is set, all
238 preceding path components of 'file' will be resolved first;
239 this flag should be set unless it is guaranteed that there is
240 no symlink in the path. When 'assume_dir' is not set, missing
241 path components will raise an ENOENT error"""
242
243 root = os.path.normpath(root)
244 file = os.path.normpath(file)
245
246 if not root.endswith(os.path.sep):
247 # letting root end with '/' makes some things easier
248 root = root + os.path.sep
249
250 if not __is_path_below(file, root):
251 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
252
253 try:
254 if use_physdir:
255 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
256 else:
257 file = __realpath(file, root, loop_cnt, assume_dir)[0]
258 except OSError as e:
259 if e.errno == errno.ELOOP:
260 # make ELOOP more readable; without catching it, there will
261 # be printed a backtrace with 100s of OSError exceptions
262 # else
263 raise OSError(errno.ELOOP,
264 "too much recursions while resolving '%s'; loop in '%s'" %
265 (file, e.strerror))
266
267 raise
268
269 return file
Brad Bishop316dfdd2018-06-25 12:45:53 -0400270
271def is_path_parent(possible_parent, *paths):
272 """
273 Return True if a path is the parent of another, False otherwise.
274 Multiple paths to test can be specified in which case all
275 specified test paths must be under the parent in order to
276 return True.
277 """
278 def abs_path_trailing(pth):
279 pth_abs = os.path.abspath(pth)
280 if not pth_abs.endswith(os.sep):
281 pth_abs += os.sep
282 return pth_abs
283
284 possible_parent_abs = abs_path_trailing(possible_parent)
285 if not paths:
286 return False
287 for path in paths:
288 path_abs = abs_path_trailing(path)
289 if not path_abs.startswith(possible_parent_abs):
290 return False
291 return True
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800292
293def which_wild(pathname, path=None, mode=os.F_OK, *, reverse=False, candidates=False):
294 """Search a search path for pathname, supporting wildcards.
295
296 Return all paths in the specific search path matching the wildcard pattern
297 in pathname, returning only the first encountered for each file. If
298 candidates is True, information on all potential candidate paths are
299 included.
300 """
301 paths = (path or os.environ.get('PATH', os.defpath)).split(':')
302 if reverse:
303 paths.reverse()
304
305 seen, files = set(), []
306 for index, element in enumerate(paths):
307 if not os.path.isabs(element):
308 element = os.path.abspath(element)
309
310 candidate = os.path.join(element, pathname)
311 globbed = glob.glob(candidate)
312 if globbed:
313 for found_path in sorted(globbed):
314 if not os.access(found_path, mode):
315 continue
316 rel = os.path.relpath(found_path, element)
317 if rel not in seen:
318 seen.add(rel)
319 if candidates:
320 files.append((found_path, [os.path.join(p, rel) for p in paths[:index+1]]))
321 else:
322 files.append(found_path)
323
324 return files
325
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600326def canonicalize(paths, sep=','):
327 """Given a string with paths (separated by commas by default), expand
328 each path using os.path.realpath() and return the resulting paths as a
329 string (separated using the same separator a the original string).
330 """
331 # Ignore paths containing "$" as they are assumed to be unexpanded bitbake
332 # variables. Normally they would be ignored, e.g., when passing the paths
333 # through the shell they would expand to empty strings. However, when they
334 # are passed through os.path.realpath(), it will cause them to be prefixed
335 # with the absolute path to the current directory and thus not be empty
336 # anymore.
337 #
338 # Also maintain trailing slashes, as the paths may actually be used as
339 # prefixes in sting compares later on, where the slashes then are important.
340 canonical_paths = []
341 for path in (paths or '').split(sep):
342 if '$' not in path:
343 trailing_slash = path.endswith('/') and '/' or ''
344 canonical_paths.append(os.path.realpath(path) + trailing_slash)
345
346 return sep.join(canonical_paths)