gen-sdk raw
1 #!/usr/bin/env python3
2 import argparse
3 import plistlib
4 import pathlib
5 import sys
6 import tarfile
7 import gzip
8 import os
9 import contextlib
10
11 @contextlib.contextmanager
12 def cd(path):
13 """Context manager that restores PWD even if an exception was raised."""
14 old_pwd = os.getcwd()
15 os.chdir(str(path))
16 try:
17 yield
18 finally:
19 os.chdir(old_pwd)
20
21 def run():
22 parser = argparse.ArgumentParser(
23 description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
24
25 parser.add_argument('xcode_app', metavar='XCODEAPP', nargs=1)
26 parser.add_argument("-o", metavar='OUTSDKTGZ', nargs=1, dest='out_sdktgz', required=False)
27
28 args = parser.parse_args()
29
30 xcode_app = pathlib.Path(args.xcode_app[0]).resolve()
31 assert xcode_app.is_dir(), "The supplied Xcode.app path '{}' either does not exist or is not a directory".format(xcode_app)
32
33 xcode_app_plist = xcode_app.joinpath("Contents/version.plist")
34 with xcode_app_plist.open('rb') as fp:
35 pl = plistlib.load(fp)
36 xcode_version = pl['CFBundleShortVersionString']
37 xcode_build_id = pl['ProductBuildVersion']
38 print("Found Xcode (version: {xcode_version}, build id: {xcode_build_id})".format(xcode_version=xcode_version, xcode_build_id=xcode_build_id))
39
40 sdk_dir = xcode_app.joinpath("Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk")
41 sdk_plist = sdk_dir.joinpath("System/Library/CoreServices/SystemVersion.plist")
42 with sdk_plist.open('rb') as fp:
43 pl = plistlib.load(fp)
44 sdk_version = pl['ProductVersion']
45 sdk_build_id = pl['ProductBuildVersion']
46 print("Found MacOSX SDK (version: {sdk_version}, build id: {sdk_build_id})".format(sdk_version=sdk_version, sdk_build_id=sdk_build_id))
47
48 out_name = "Xcode-{xcode_version}-{xcode_build_id}-extracted-SDK-with-libcxx-headers".format(xcode_version=xcode_version, xcode_build_id=xcode_build_id)
49
50 if args.out_sdktgz:
51 out_sdktgz_path = pathlib.Path(args.out_sdktgz_path)
52 else:
53 # Construct our own out_sdktgz if not specified on the command line
54 out_sdktgz_path = pathlib.Path("./{}.tar.gz".format(out_name))
55
56 def tarfp_add_with_base_change(tarfp, dir_to_add, alt_base_dir):
57 """Add all files in dir_to_add to tarfp, but prepent alt_base_dir to the files'
58 names
59
60 e.g. if the only file under /root/bazdir is /root/bazdir/qux, invoking:
61
62 tarfp_add_with_base_change(tarfp, "foo/bar", "/root/bazdir")
63
64 would result in the following members being added to tarfp:
65
66 foo/bar/ -> corresponding to /root/bazdir
67 foo/bar/qux -> corresponding to /root/bazdir/qux
68
69 """
70 def change_tarinfo_base(tarinfo):
71 if tarinfo.name and tarinfo.name.startswith("./"):
72 tarinfo.name = str(pathlib.Path(alt_base_dir, tarinfo.name))
73 if tarinfo.linkname and tarinfo.linkname.startswith("./"):
74 tarinfo.linkname = str(pathlib.Path(alt_base_dir, tarinfo.linkname))
75 # make metadata deterministic
76 tarinfo.mtime = 0
77 tarinfo.uid, tarinfo.uname = 0, ''
78 tarinfo.gid, tarinfo.gname = 0, ''
79 # don't use isdir() as there are also executable files present
80 tarinfo.mode = 0o0755 if tarinfo.mode & 0o0100 else 0o0644
81 return tarinfo
82 with cd(dir_to_add):
83 # recursion already adds entries in sorted order
84 tarfp.add(".", recursive=True, filter=change_tarinfo_base)
85
86 print("Creating output .tar.gz file...")
87 with out_sdktgz_path.open("wb") as fp:
88 with gzip.GzipFile(fileobj=fp, mode='wb', compresslevel=9, mtime=0) as gzf:
89 with tarfile.open(mode="w", fileobj=gzf, format=tarfile.GNU_FORMAT) as tarfp:
90 print("Adding MacOSX SDK {} files...".format(sdk_version))
91 tarfp_add_with_base_change(tarfp, sdk_dir, out_name)
92 print("Done! Find the resulting gzipped tarball at:")
93 print(out_sdktgz_path.resolve())
94
95 if __name__ == '__main__':
96 run()
97