gen-sdk.py raw

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