|
| 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 | + xcode_libcxx_dir = xcode_app.joinpath("Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1") |
| 51 | + assert xcode_libcxx_dir.is_dir() |
| 52 | + |
| 53 | + if args.out_sdktgz: |
| 54 | + out_sdktgz_path = pathlib.Path(args.out_sdktgz_path) |
| 55 | + else: |
| 56 | + # Construct our own out_sdktgz if not specified on the command line |
| 57 | + out_sdktgz_path = pathlib.Path("./{}.tar.gz".format(out_name)) |
| 58 | + |
| 59 | + def tarfp_add_with_base_change(tarfp, dir_to_add, alt_base_dir): |
| 60 | + """Add all files in dir_to_add to tarfp, but prepent MEMBERPREFIX to the files' |
| 61 | + names |
| 62 | +
|
| 63 | + e.g. if the only file under /root/bazdir is /root/bazdir/qux, invoking: |
| 64 | +
|
| 65 | + tarfp_add_with_base_change(tarfp, "foo/bar", "/root/bazdir") |
| 66 | +
|
| 67 | + would result in the following members being added to tarfp: |
| 68 | +
|
| 69 | + foo/bar/ -> corresponding to /root/bazdir |
| 70 | + foo/bar/qux -> corresponding to /root/bazdir/qux |
| 71 | +
|
| 72 | + """ |
| 73 | + def change_tarinfo_base(tarinfo): |
| 74 | + if tarinfo.name and tarinfo.name.startswith("./"): |
| 75 | + tarinfo.name = str(pathlib.Path(alt_base_dir, tarinfo.name)) |
| 76 | + if tarinfo.linkname and tarinfo.linkname.startswith("./"): |
| 77 | + tarinfo.linkname = str(pathlib.Path(alt_base_dir, tarinfo.linkname)) |
| 78 | + return tarinfo |
| 79 | + with cd(dir_to_add): |
| 80 | + tarfp.add(".", recursive=True, filter=change_tarinfo_base) |
| 81 | + |
| 82 | + print("Creating output .tar.gz file...") |
| 83 | + with out_sdktgz_path.open("wb") as fp: |
| 84 | + with gzip.GzipFile(fileobj=fp, compresslevel=9, mtime=0) as gzf: |
| 85 | + with tarfile.open(mode="w", fileobj=gzf) as tarfp: |
| 86 | + print("Adding MacOSX SDK {} files...".format(sdk_version)) |
| 87 | + tarfp_add_with_base_change(tarfp, sdk_dir, out_name) |
| 88 | + print("Adding libc++ headers...") |
| 89 | + tarfp_add_with_base_change(tarfp, xcode_libcxx_dir, "{}/usr/include/c++/v1".format(out_name)) |
| 90 | + print("Done! Find the resulting gzipped tarball at:") |
| 91 | + print(out_sdktgz_path.resolve()) |
| 92 | + |
| 93 | +if __name__ == '__main__': |
| 94 | + run() |
0 commit comments