1CHRPATH_BIN ?= "chrpath"
2PREPROCESS_RELOCATE_DIRS ?= ""
3
4def process_file_linux(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = False):
5    import subprocess, oe.qa
6
7    with oe.qa.ELFFile(fpath) as elf:
8        try:
9            elf.open()
10        except oe.qa.NotELFFileError:
11            return
12
13    try:
14        out = subprocess.check_output([cmd, "-l", fpath], universal_newlines=True)
15    except subprocess.CalledProcessError:
16        return
17
18    # Handle RUNPATH as well as RPATH
19    out = out.replace("RUNPATH=","RPATH=")
20    # Throw away everything other than the rpath list
21    curr_rpath = out.partition("RPATH=")[2]
22    #bb.note("Current rpath for %s is %s" % (fpath, curr_rpath.strip()))
23    rpaths = curr_rpath.strip().split(":")
24    new_rpaths = []
25    modified = False
26    for rpath in rpaths:
27        # If rpath is already dynamic copy it to new_rpath and continue
28        if rpath.find("$ORIGIN") != -1:
29            new_rpaths.append(rpath)
30            continue
31        rpath =  os.path.normpath(rpath)
32        if baseprefix not in rpath and tmpdir not in rpath:
33            # Skip standard search paths
34            if rpath in ['/lib', '/usr/lib', '/lib64/', '/usr/lib64']:
35                bb.warn("Skipping RPATH %s as is a standard search path for %s" % (rpath, fpath))
36                modified = True
37                continue
38            new_rpaths.append(rpath)
39            continue
40        new_rpaths.append("$ORIGIN/" + os.path.relpath(rpath, os.path.dirname(fpath.replace(rootdir, "/"))))
41        modified = True
42
43    # if we have modified some rpaths call chrpath to update the binary
44    if modified:
45        if break_hardlinks:
46            bb.utils.break_hardlinks(fpath)
47
48        args = ":".join(new_rpaths)
49        #bb.note("Setting rpath for %s to %s" %(fpath, args))
50        try:
51            subprocess.check_output([cmd, "-r", args, fpath],
52            stderr=subprocess.PIPE, universal_newlines=True)
53        except subprocess.CalledProcessError as e:
54            bb.fatal("chrpath command failed with exit code %d:\n%s\n%s" % (e.returncode, e.stdout, e.stderr))
55
56def process_file_darwin(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = False):
57    import subprocess as sub
58
59    p = sub.Popen([d.expand("${HOST_PREFIX}otool"), '-L', fpath],stdout=sub.PIPE,stderr=sub.PIPE)
60    out, err = p.communicate()
61    # If returned successfully, process stdout for results
62    if p.returncode != 0:
63        return
64    for l in out.split("\n"):
65        if "(compatibility" not in l:
66            continue
67        rpath = l.partition("(compatibility")[0].strip()
68        if baseprefix not in rpath:
69            continue
70
71        if break_hardlinks:
72            bb.utils.break_hardlinks(fpath)
73
74        newpath = "@loader_path/" + os.path.relpath(rpath, os.path.dirname(fpath.replace(rootdir, "/")))
75        p = sub.Popen([d.expand("${HOST_PREFIX}install_name_tool"), '-change', rpath, newpath, fpath],stdout=sub.PIPE,stderr=sub.PIPE)
76        out, err = p.communicate()
77
78def process_dir(rootdir, directory, d, break_hardlinks = False):
79    bb.debug(2, "Checking %s for binaries to process" % directory)
80    if not os.path.exists(directory):
81        return
82
83    import stat
84
85    rootdir = os.path.normpath(rootdir)
86    cmd = d.expand('${CHRPATH_BIN}')
87    tmpdir = os.path.normpath(d.getVar('TMPDIR', False))
88    baseprefix = os.path.normpath(d.expand('${base_prefix}'))
89    hostos = d.getVar("HOST_OS")
90
91    if "linux" in hostos:
92        process_file = process_file_linux
93    elif "darwin" in hostos:
94        process_file = process_file_darwin
95    else:
96        # Relocations not supported
97        return
98
99    dirs = os.listdir(directory)
100    for file in dirs:
101        fpath = directory + "/" + file
102        fpath = os.path.normpath(fpath)
103        if os.path.islink(fpath):
104            # Skip symlinks
105            continue
106
107        if os.path.isdir(fpath):
108            process_dir(rootdir, fpath, d, break_hardlinks = break_hardlinks)
109        else:
110            #bb.note("Testing %s for relocatability" % fpath)
111
112            # We need read and write permissions for chrpath, if we don't have
113            # them then set them temporarily. Take a copy of the files
114            # permissions so that we can restore them afterwards.
115            perms = os.stat(fpath)[stat.ST_MODE]
116            if os.access(fpath, os.W_OK|os.R_OK):
117                perms = None
118            else:
119                # Temporarily make the file writeable so we can chrpath it
120                os.chmod(fpath, perms|stat.S_IRWXU)
121
122            process_file(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = break_hardlinks)
123
124            if perms:
125                os.chmod(fpath, perms)
126
127def rpath_replace (path, d):
128    bindirs = d.expand("${bindir} ${sbindir} ${base_sbindir} ${base_bindir} ${libdir} ${base_libdir} ${libexecdir} ${PREPROCESS_RELOCATE_DIRS}").split()
129
130    for bindir in bindirs:
131        #bb.note ("Processing directory " + bindir)
132        directory = path + "/" + bindir
133        process_dir (path, directory, d)
134
135