diff --git a/utils/api_checker/sdk-module-lists/fixed-modules-appletvos.txt b/utils/api_checker/sdk-module-lists/fixed-modules-appletvos.txt new file mode 100644 index 0000000000000..5154bd4dff5d3 --- /dev/null +++ b/utils/api_checker/sdk-module-lists/fixed-modules-appletvos.txt @@ -0,0 +1 @@ +XCTest \ No newline at end of file diff --git a/utils/api_checker/sdk-module-lists/fixed-modules-common.txt b/utils/api_checker/sdk-module-lists/fixed-modules-common.txt new file mode 100644 index 0000000000000..05e3b03896587 --- /dev/null +++ b/utils/api_checker/sdk-module-lists/fixed-modules-common.txt @@ -0,0 +1,6 @@ +Darwin +Dispatch +MachO +ObjectiveC +libkern +os diff --git a/utils/api_checker/sdk-module-lists/fixed-modules-iphoneos.txt b/utils/api_checker/sdk-module-lists/fixed-modules-iphoneos.txt new file mode 100644 index 0000000000000..5154bd4dff5d3 --- /dev/null +++ b/utils/api_checker/sdk-module-lists/fixed-modules-iphoneos.txt @@ -0,0 +1 @@ +XCTest \ No newline at end of file diff --git a/utils/api_checker/sdk-module-lists/fixed-modules-macosx.txt b/utils/api_checker/sdk-module-lists/fixed-modules-macosx.txt new file mode 100644 index 0000000000000..14aaee023d9ca --- /dev/null +++ b/utils/api_checker/sdk-module-lists/fixed-modules-macosx.txt @@ -0,0 +1,2 @@ +XPC +XCTest diff --git a/utils/api_checker/sdk-module-lists/fixed-modules-watchos.txt b/utils/api_checker/sdk-module-lists/fixed-modules-watchos.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/utils/api_checker/sdk-module-lists/infer-imports.py b/utils/api_checker/sdk-module-lists/infer-imports.py new file mode 100755 index 0000000000000..2c38c1577f098 --- /dev/null +++ b/utils/api_checker/sdk-module-lists/infer-imports.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python -u + +import os +import sys + +blacklist = ["Kernel", "Ruby", "Tk"] + + +def get_immediate_subdirectories(a_dir): + return [name for name in os.listdir(a_dir) + if os.path.isdir(os.path.join(a_dir, name))] + + +def get_frameworks(sdk_path): + frameworks_path = sdk_path + "/System/Library/Frameworks" + names = [] + for frame in os.listdir(frameworks_path): + if frame.endswith(".framework"): + header_dir_path = frameworks_path + '/' + frame + '/Headers' + module_dir_path = frameworks_path + '/' + frame + '/Modules' + old_modulemap_path = frameworks_path + '/' + frame + '/module.map' + old_modulemap_private_path = frameworks_path + '/' + frame + \ + '/module_private.map' + if not os.path.exists(header_dir_path): + if os.path.exists(module_dir_path): + print >>sys.stderr, header_dir_path, \ + " non-existent while 'Modules' exists" + if os.path.exists(old_modulemap_path): + print >>sys.stderr, header_dir_path, \ + " non-existent while 'module.map' exists" + if os.path.exists(old_modulemap_private_path): + print >>sys.stderr, header_dir_path, \ + " non-existent while 'module_private.map' exists" + continue + + if should_exclude_framework(frameworks_path + '/' + frame): + continue + + name = frame[:-len(".framework")] + if name in blacklist: + continue + names.append(name) + return names + + +def should_exclude_framework(frame_path): + module_map_path = frame_path + '/Modules/module.modulemap' + if not os.path.exists(module_map_path): + return False + contents = open(module_map_path).read() + if "requires !swift" in contents: + return True + if "requires unavailable" in contents: + return True + + return False + + +def print_clang_imports(frames, use_hash): + for name in frames: + if use_hash: + print "#import <" + name + "/" + name + ".h>" + else: + print "@import " + name + ";" + + +def print_swift_imports(frames): + for name in frames: + print "import " + name + + +def main(): + global opts + from optparse import OptionParser + parser = OptionParser("""%prog [options] command + +%prog outputs imports + + $ %prog -s -o [--hash] + +""") + parser.add_option("-s", "--sdk", help="sdk path", + type=str, dest="sdk", default=None) + parser.add_option("-o", "--output", help="output mode", + type=str, dest="out_mode", default="list") + parser.add_option("--hash", action="store_true", dest="use_hash") + + (opts, cmd) = parser.parse_args() + + if not opts.sdk: + parser.error("sdk not specified") + + if not opts.out_mode: + parser.error( + "output mode not specified: 'clang-import'/'swift-import'/'list'") + + frames = get_frameworks(opts.sdk) + if opts.out_mode == "clang-import": + print_clang_imports(frames, opts.use_hash) + elif opts.out_mode == "swift-import": + print_swift_imports(frames) + elif opts.out_mode == "list": + for name in frames: + print name + else: + parser.error( + "output mode not found: 'clang-import'/'swift-import'/'list'") + + +if __name__ == '__main__': + main() diff --git a/utils/api_checker/swift-api-checker.py b/utils/api_checker/swift-api-checker.py new file mode 100755 index 0000000000000..99c3779f35b23 --- /dev/null +++ b/utils/api_checker/swift-api-checker.py @@ -0,0 +1,144 @@ +#!/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=True, output=None): + if verbose: + print(' '.join([escapeCmdArg(arg) for arg in cmd])) + return subprocess.check_call(cmd, cwd=cwd, env=env, + stderr=subprocess.STDOUT, stdout=output) + + +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): + return check_output(['xcrun', '-sdk', platform, '-show-sdk-path']) + + +def prepare_module_list(platform, file): + check_call([INFER_IMPORT_PATH, '-s', get_sdk_path(platform)], output=file) + with open(INFER_IMPORT_DIR + '/fixed-modules-common.txt', 'r') as extra: + file.write(extra.read()) + with open(INFER_IMPORT_DIR + '/fixed-modules-' + platform + '.txt', + 'r') as extra: + file.write(extra.read()) + + +class DumpConfig: + def __init__(self, platform): + target_map = { + 'iphoneos': 'arm64-apple-ios10.0', + 'macosx': 'x86_64-apple-macosx10.11', + 'appletvos': 'arm64-apple-tvos10.0', + 'watchos': 'armv7k-apple-watchos3.0', + } + self.platform = platform + self.target = target_map[platform] + self.sdk = get_sdk_path(platform) + self.frameworks = [ + self.sdk + '/System/Library/Frameworks/', + os.path.realpath(self.sdk + '/../../Library/Frameworks/')] + self.tool_path = check_output(['xcrun', '--find', + 'swift-api-digester']) + + def run(self, output, module, swift_ver, abi): + cmd = [self.tool_path, '-o', output, '-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]) + if abi: + cmd.extend(['-abi']) + if module: + cmd.extend(['-module', module]) + check_call(cmd) + else: + with tempfile.NamedTemporaryFile() as tmp: + prepare_module_list(self.platform, tmp) + cmd.extend(['-module-list-file', tmp.name]) + check_call(cmd) + + +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('--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='4', help=''' + Swift version to use; default is 4 + ''') + + basic_group.add_argument('--module', default=None, help=''' + name of the module/framework to generate baseline, e.g. Foundation + ''') + + basic_group.add_argument('--abi', + action='store_true', + help='Whether we are jsonizing for abi') + + args = parser.parse_args(sys.argv[1:]) + if not args.target: + fatal_error("Need to specify --target") + if not args.output: + fatal_error("Need to specify --output") + + if args.action == 'dump': + runner = DumpConfig(platform=args.target) + runner.run(output=args.output, module=args.module, + swift_ver=args.swift_version, abi=args.abi) + elif args.action == 'diagnose': + fatal_error('Not implemented') + else: + fatal_error('Cannot recognize action: ' + args.action) + + +if __name__ == '__main__': + main()