1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 from argparse
import ArgumentParser
11 from pathlib
import Path
13 from buildconfig
import substs
16 Scans the given directories for binaries referencing the AddressSanitizer
17 runtime library, copies it to the main directory.
20 # This is the dylib name pattern
21 DYLIB_NAME_PATTERN
= re
.compile(r
"libclang_rt\.(a|ub)san_osx_dynamic\.dylib")
24 def resolve_rpath(filename
):
25 otoolOut
= subprocess
.check_output([substs
["OTOOL"], "-l", filename
], text
=True)
28 # The lines we need to find look like this:
33 # path /home/build/src/clang/bin/../lib/clang/3.8.0/lib/darwin (offset 12)
36 # Other load command types have a varying number of fields.
37 for line
in otoolOut
.splitlines():
38 cmdMatch
= re
.match(r
"^\s+cmd ([A-Z_]+)", line
)
39 if cmdMatch
is not None:
40 currentCmd
= cmdMatch
.group(1)
43 if currentCmd
== "LC_RPATH":
44 pathMatch
= re
.match(r
"^\s+path (.*) \(offset \d+\)", line
)
45 if pathMatch
is not None:
46 path
= pathMatch
.group(1)
47 if Path(path
).is_dir():
50 print(f
"@rpath could not be resolved from {filename}", file=sys
.stderr
)
54 def scan_directory(path
):
56 dylibsRequired
= set()
59 print(f
"Input path {path} is not a folder", file=sys
.stderr
)
62 for file in path
.rglob("*"):
63 if not file.is_file():
66 # Skip all files that aren't either dylibs or executable
67 if not (file.suffix
== ".dylib" or os
.access(str(file), os
.X_OK
)):
71 otoolOut
= subprocess
.check_output(
72 [substs
["OTOOL"], "-L", str(file)], text
=True
75 # Errors are expected on non-mach executables, ignore them and continue
78 for line
in otoolOut
.splitlines():
79 match
= DYLIB_NAME_PATTERN
.search(line
)
82 dylibName
= match
.group(0)
83 absDylibPath
= line
.split()[0]
85 dylibsRequired
.add(dylibName
)
87 if dylibName
not in dylibsCopied
:
88 if absDylibPath
.startswith("@rpath/"):
89 rpath
= resolve_rpath(str(file))
90 copyDylibPath
= absDylibPath
.replace("@rpath", rpath
)
92 copyDylibPath
= absDylibPath
94 if Path(copyDylibPath
).is_file():
95 # Copy the runtime once to the main directory, which is passed
96 # as the argument to this function.
97 shutil
.copy(copyDylibPath
, str(path
))
98 dylibsCopied
.add(dylibName
)
101 f
"dylib path in {file} was not found at: {copyDylibPath}",
107 dylibsMissing
= dylibsRequired
- dylibsCopied
109 for dylibName
in dylibsMissing
:
110 print(f
"{dylibName} could not be found", file=sys
.stderr
)
114 def parse_args(argv
=None):
115 parser
= ArgumentParser()
116 parser
.add_argument("paths", metavar
="path", type=Path
, nargs
="+")
117 return parser
.parse_args(argv
)
120 if __name__
== "__main__":