| #!/usr/bin/env python |
| |
| from __future__ import print_function |
| |
| import argparse |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| |
| INFER_IMPORT_DIR = \ |
| os.path.dirname(os.path.realpath(__file__)) + '/sdk-module-lists' |
| |
| INFER_IMPORT_PATH = INFER_IMPORT_DIR + '/infer-imports.py' |
| |
| |
| def printerr(message): |
| print(message, file=sys.stderr) |
| |
| |
| def fatal_error(message): |
| printerr(message) |
| sys.exit(1) |
| |
| |
| def escapeCmdArg(arg): |
| if '"' in arg or ' ' in arg: |
| return '"%s"' % arg.replace('"', '\\"') |
| else: |
| return arg |
| |
| |
| def check_call(cmd, cwd=None, env=os.environ, verbose=False, output=None): |
| if verbose: |
| print(' '.join([escapeCmdArg(arg) for arg in cmd])) |
| try: |
| subprocess.check_call(cmd, cwd=cwd, env=env, |
| stderr=None, stdout=output) |
| return 0 |
| except Exception as error: |
| printerr(error) |
| return 1 |
| |
| |
| def check_output(cmd, verbose=False): |
| if verbose: |
| print(' '.join([escapeCmdArg(arg) for arg in cmd])) |
| return subprocess.check_output(cmd).strip() |
| |
| |
| def get_sdk_path(platform): |
| if platform.startswith('iosmac'): |
| platform = 'macosx' |
| return check_output(['xcrun', '-sdk', platform, '-show-sdk-path']) |
| |
| |
| def write_fixed_module(file, platform, infix, verbose): |
| common_modules_path = os.path.join(INFER_IMPORT_DIR, 'fixed-' + infix + |
| '-modules-common.txt') |
| platform_modules_path = os.path.join(INFER_IMPORT_DIR, 'fixed-' + infix + |
| '-modules-' + platform + '.txt') |
| with open(common_modules_path, 'r') as extra: |
| if verbose: |
| print('Including modules in: ' + common_modules_path) |
| file.write(extra.read()) |
| with open(platform_modules_path, 'r') as extra: |
| if verbose: |
| print('Including modules in: ' + platform_modules_path) |
| file.write(extra.read()) |
| |
| |
| def prepare_module_list(platform, file, verbose, module_filter_flags, |
| include_fixed_clang_modules): |
| cmd = [INFER_IMPORT_PATH, '-s', get_sdk_path(platform)] |
| cmd.extend(module_filter_flags) |
| if platform.startswith('iosmac'): |
| cmd.extend(['--catalyst']) |
| if verbose: |
| cmd.extend(['--v']) |
| check_call(cmd, verbose=verbose, output=file) |
| # Always include fixed swift modules |
| write_fixed_module(file, platform, 'swift', verbose) |
| # Check if we need fixed clang modules |
| if include_fixed_clang_modules: |
| write_fixed_module(file, platform, 'clang', verbose) |
| |
| |
| def get_api_digester_path(tool_path): |
| if tool_path: |
| return tool_path |
| return check_output(['xcrun', '--find', 'swift-api-digester']) |
| |
| |
| def create_directory(path): |
| if not os.path.isdir(path): |
| os.makedirs(path) |
| |
| |
| class DumpConfig: |
| def __init__(self, tool_path, platform, platform_alias, abi, verbose): |
| target_map = { |
| 'iphoneos': 'arm64-apple-ios13.0', |
| 'macosx': 'x86_64-apple-macosx10.15', |
| 'appletvos': 'arm64-apple-tvos13.0', |
| 'watchos': 'armv7k-apple-watchos6.0', |
| 'iosmac': 'x86_64-apple-ios13.0-macabi', |
| } |
| self.tool_path = get_api_digester_path(tool_path) |
| self.platform = platform |
| self.target = target_map[platform] |
| self.sdk = get_sdk_path(platform) |
| self.inputs = [] |
| self.platform_alias = platform_alias |
| self.abi = abi |
| if self.platform == 'macosx': |
| # We need this input search path for CreateML |
| self.inputs.extend([self.sdk + '/usr/lib/swift/']) |
| # This is where XCTest is |
| self.frameworks = [self.sdk + '/../../Library/Frameworks/'] |
| if self.platform.startswith('iosmac'): |
| # Catalyst modules need this extra framework dir |
| iOSSupport = self.sdk + \ |
| '/System/iOSSupport/System/Library/Frameworks' |
| self.frameworks.extend([iOSSupport]) |
| self._environ = dict(os.environ) |
| self._environ['SWIFT_FORCE_MODULE_LOADING'] = 'prefer-interface' |
| self.verbose = verbose |
| |
| def dumpZipperedContent(self, cmd, output, module): |
| dir_path = os.path.realpath(output + '/' + module) |
| if self.abi: |
| dir_path = os.path.join(dir_path, 'ABI') |
| else: |
| dir_path = os.path.join(dir_path, 'API') |
| file_path = os.path.realpath(dir_path + '/' + self.platform_alias + |
| '.json') |
| create_directory(dir_path) |
| current_cmd = list(cmd) |
| current_cmd.extend(['-module', module]) |
| current_cmd.extend(['-o', file_path]) |
| check_call(current_cmd, env=self._environ, verbose=self.verbose) |
| |
| def run(self, output, module, swift_ver, opts, |
| module_filter_flags, include_fixed_clang_modules, |
| separate_by_module, zippered): |
| cmd = [self.tool_path, '-sdk', self.sdk, '-target', |
| self.target, '-dump-sdk', '-module-cache-path', |
| '/tmp/ModuleCache', '-swift-version', |
| swift_ver, '-abort-on-module-fail'] |
| for path in self.frameworks: |
| cmd.extend(['-iframework', path]) |
| for path in self.inputs: |
| cmd.extend(['-I', path]) |
| if self.abi: |
| cmd.extend(['-abi', '-swift-only']) |
| cmd.extend(['-' + o for o in opts]) |
| if self.verbose: |
| cmd.extend(['-v']) |
| if module: |
| if zippered: |
| create_directory(output) |
| self.dumpZipperedContent(cmd, output, module) |
| else: |
| cmd.extend(['-module', module]) |
| cmd.extend(['-o', output]) |
| check_call(cmd, env=self._environ, verbose=self.verbose) |
| else: |
| with tempfile.NamedTemporaryFile() as tmp: |
| prepare_module_list(self.platform, tmp, self.verbose, |
| module_filter_flags, |
| include_fixed_clang_modules) |
| if separate_by_module: |
| tmp.seek(0) |
| create_directory(output) |
| for module in [name.strip() for name in tmp.readlines()]: |
| # Skip comments |
| if module.startswith('//'): |
| continue |
| self.dumpZipperedContent(cmd, output, module) |
| else: |
| cmd.extend(['-o', output]) |
| cmd.extend(['-module-list-file', tmp.name]) |
| check_call(cmd, env=self._environ, verbose=self.verbose) |
| |
| |
| class DiagnoseConfig: |
| def __init__(self, tool_path, abi): |
| self.tool_path = get_api_digester_path(tool_path) |
| self.abi = abi |
| |
| def run(self, opts, before, after, output, verbose): |
| cmd = [self.tool_path, '-diagnose-sdk', '-input-paths', before, |
| '-input-paths', after, '-print-module'] |
| if output: |
| cmd.extend(['-o', output]) |
| if self.abi: |
| cmd.extend(['-abi']) |
| cmd.extend(['-' + o for o in opts]) |
| if verbose: |
| cmd.extend(['-v']) |
| check_call(cmd, verbose=verbose) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| description=''' |
| A convenient wrapper for swift-api-digester. |
| ''') |
| |
| basic_group = parser.add_argument_group('Basic') |
| |
| basic_group.add_argument('--tool-path', default=None, help=''' |
| the path to a swift-api-digester; if not specified, the script will |
| use the one from the toolchain |
| ''') |
| |
| basic_group.add_argument('--action', default='', help=''' |
| the action to perform for swift-api-digester |
| ''') |
| |
| basic_group.add_argument('--target', default=None, help=''' |
| one of macosx, iphoneos, appletvos, and watchos |
| ''') |
| |
| basic_group.add_argument('--output', default=None, help=''' |
| the output file of the module baseline should end with .json |
| ''') |
| |
| basic_group.add_argument('--swift-version', default='5', help=''' |
| Swift version to use; default is 5 |
| ''') |
| |
| basic_group.add_argument('--module', default=None, help=''' |
| name of the module/framework to generate baseline, e.g. Foundation |
| ''') |
| |
| basic_group.add_argument('--module-filter', default='', help=''' |
| the action to perform for swift-api-digester |
| ''') |
| |
| basic_group.add_argument('--opts', nargs='+', default=[], help=''' |
| additional flags to pass to swift-api-digester |
| ''') |
| |
| basic_group.add_argument('--v', |
| action='store_true', |
| help='Process verbosely') |
| |
| basic_group.add_argument('--dump-before', |
| action=None, |
| help=''' |
| Path to the json file generated before change' |
| ''') |
| |
| basic_group.add_argument('--dump-after', |
| action=None, |
| help=''' |
| Path to the json file generated after change |
| ''') |
| |
| basic_group.add_argument('--separate-by-module', |
| action='store_true', |
| help='When importing entire SDK, dump content ' |
| 'seprately by module names') |
| |
| basic_group.add_argument('--zippered', |
| action='store_true', |
| help='dump module content to a dir with files for' |
| 'seprately targets') |
| |
| basic_group.add_argument('--abi', |
| action='store_true', |
| help='Process verbosely') |
| |
| basic_group.add_argument('--platform-alias', default='', help=''' |
| Specify a file name to use if using a platform name in json file isn't |
| optimal |
| ''') |
| |
| args = parser.parse_args(sys.argv[1:]) |
| |
| if args.action == 'dump': |
| if not args.target: |
| fatal_error("Need to specify --target") |
| if not args.output: |
| fatal_error("Need to specify --output") |
| if args.module_filter == '': |
| module_filter_flags = [] |
| include_fixed_clang_modules = True |
| elif args.module_filter == 'swift-frameworks-only': |
| module_filter_flags = ['--swift-frameworks-only'] |
| include_fixed_clang_modules = False |
| elif args.module_filter == 'swift-overlay-only': |
| module_filter_flags = ['--swift-overlay-only'] |
| include_fixed_clang_modules = False |
| else: |
| fatal_error("cannot recognize --module-filter") |
| if args.platform_alias == '': |
| args.platform_alias = args.target |
| runner = DumpConfig(tool_path=args.tool_path, platform=args.target, |
| platform_alias=args.platform_alias, |
| abi=args.abi, |
| verbose=args.v) |
| runner.run(output=args.output, module=args.module, |
| swift_ver=args.swift_version, opts=args.opts, |
| module_filter_flags=module_filter_flags, |
| include_fixed_clang_modules=include_fixed_clang_modules, |
| separate_by_module=args.separate_by_module, |
| zippered=args.zippered) |
| elif args.action == 'diagnose': |
| if not args.dump_before: |
| fatal_error("Need to specify --dump-before") |
| if not args.dump_after: |
| fatal_error("Need to specify --dump-after") |
| runner = DiagnoseConfig(tool_path=args.tool_path, abi=args.abi) |
| runner.run(opts=args.opts, before=args.dump_before, |
| after=args.dump_after, output=args.output, verbose=args.v) |
| else: |
| fatal_error('Cannot recognize action: ' + args.action) |
| |
| |
| if __name__ == '__main__': |
| main() |