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