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