File: swift-rpathize.py

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (97 lines) | stat: -rwxr-xr-x 4,031 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/env python3

# On Darwin, dynamic libraries have an install name.  At link time, the
# linker can work with a dylib anywhere in the filesystem, but it will
# write the dylib's install name into the resulting image, and at load
# time that dylib will normally be expected to be found at exactly that
# path.  However, if the install name in an image begins with `@rpath`,
# it will instead be searched for in the image's runtime search path
# list.  That list may contain absolute paths, but it may also contain
# paths beginning with `@executable_path` or `@loader_path`, meaning the
# path containing the running executable or the image being loaded,
# respectively.
#
# Many of Swift's dylibs are meant to be installed on the system, which
# means they have install names like this:
#   /usr/lib/swift/libswiftFoo.dylib
# To support back-deployment, they also provide magic override symbols
# ($ld$install_name) for all the OS versions preceding the addition of
# of the library.  When the linker finds a dylib with a matching override
# for the OS deployment target, it ignores the normal install name and
# uses the override path in the linked image's load command.  Swift's
# libraries use override paths that begin with `@rpath`, and Swift
# builds images with a runtime search path list that starts with
# /usr/lib/swift but then falls back on a path relative to the image;
# thus, apps will use the system libraries if available but will
# otherwise use fallback libraries.
#
# When we're working on Swift, we usually want to test the libraries
# we just built rather than the system libraries.  There are two ways
# to achieve that.  The first is to override dyld's runtime search path
# with DYLD_LIBRARY_PATH; this will take precedence over even an
# absolute install name.  The second is to make sure the dylibs are
# loaded via an @rpath install name and then link the program with an
# rpath that will use the just-built libraries.  Unfortunately, the
# toolchain will ordinarily use an absolute install name instead of
# an @rpath if the deployment target is old enough, subverting testing.
#
# This script looks for dependent dylibs with an absolute path in
# /usr/lib/swift and changes them to use @rpath.

import argparse
import re
import subprocess
import sys


def main(arguments):
    parser = argparse.ArgumentParser(
        description='Change absolute install names to use @rpath')
    parser.add_argument('bin', help='the binary')

    args = parser.parse_args(arguments)
    rpathize(args.bin)


def rpathize(filename):
    dylibsOutput = None

    try:
        # `dyldinfo` has been replaced with `dyld_info`, so we try it first
        # before falling back to `dyldinfo`
        dylibsOutput = subprocess.check_output(
            ['xcrun', 'dyld_info', '-dependents', filename],
            universal_newlines=True)
    except subprocess.CalledProcessError:
        sys.stderr.write("falling back to 'xcrun dyldinfo' ...\n")
        dylibsOutput = subprocess.check_output(
            ['xcrun', 'dyldinfo', '-dylibs', filename],
            universal_newlines=True)

    # The output from dyldinfo -dylibs is a line of header followed by one
    # install name per line, indented with spaces.
    dylib_regex = re.compile(
        r"(^|.*\s)(?P<path>/usr/lib/swift/(?P<filename>.*\.dylib))\s*$")

    # Build a command to invoke install_name_tool.
    command = ['install_name_tool']
    for line in dylibsOutput.splitlines():
        match = dylib_regex.match(line)
        if match:
            command.append('-change')
            command.append(match.group('path'))
            command.append('@rpath/' + match.group('filename'))
            continue

    # Don't run the command if we didn't find any dylibs to change:
    # it's invalid to invoke install_name_tool without any operations.
    if len(command) == 1:
        return

    # The last argument is the filename to operate on.
    command.append(filename)

    subprocess.check_call(command)


sys.exit(main(sys.argv[1:]))