| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import json |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| |
| |
| def fix_module_imports(header_path, output_path): |
| """Convert modules import to work without -fmodules support. |
| |
| The Swift compiler assumes that the generated Objective-C header will be |
| imported from code compiled with module support enabled (-fmodules). The |
| generated code thus uses @import and provides no fallback if modules are |
| not enabled. |
| |
| This function converts the generated header to instead use #import. It |
| assumes that `@import Foo;` can be replaced by `#import <Foo/Foo.h>`. |
| |
| The header is read at `header_path` and written to `output_path`. |
| """ |
| |
| header_contents = [] |
| with open(header_path, 'r') as header_file: |
| for line in header_file: |
| if line == '#if __has_feature(modules)\n': |
| header_contents.append('#if 1 // #if __has_feature(modules)\n') |
| nesting_level = 1 |
| for line in header_file: |
| if line == '#endif\n': |
| nesting_level -= 1 |
| elif line.startswith('@import'): |
| name = line.split()[1].split(';')[0] |
| if name != 'ObjectiveC': |
| header_contents.append(f'#import <{name}/{name}.h> ') |
| header_contents.append('// ') |
| elif line.startswith('#if'): |
| nesting_level += 1 |
| |
| header_contents.append(line) |
| if nesting_level == 0: |
| break |
| else: |
| header_contents.append(line) |
| |
| with open(output_path, 'w') as header_file: |
| for line in header_contents: |
| header_file.write(line) |
| |
| |
| def compile_module(module, sources, settings, extras, tmpdir): |
| """Compile `module` from `sources` using `settings`.""" |
| output_file_map = {} |
| if settings.whole_module_optimization: |
| output_file_map[''] = { |
| 'object': os.path.join(settings.object_dir, module + '.o'), |
| 'dependencies': os.path.join(tmpdir, module + '.d'), |
| } |
| else: |
| for source in sources: |
| name, _ = os.path.splitext(os.path.basename(source)) |
| output_file_map[source] = { |
| 'object': os.path.join(settings.object_dir, name + '.o'), |
| 'dependencies': os.path.join(tmpdir, name + '.d'), |
| } |
| |
| for key in ('module_path', 'header_path', 'depfile'): |
| path = getattr(settings, key) |
| if os.path.exists(path): |
| os.unlink(path) |
| if key == 'module_path': |
| for ext in '.swiftdoc', '.swiftsourceinfo': |
| path = os.path.splitext(getattr(settings, key))[0] + ext |
| if os.path.exists(path): |
| os.unlink(path) |
| directory = os.path.dirname(path) |
| if not os.path.exists(directory): |
| os.makedirs(directory) |
| |
| if not os.path.exists(settings.object_dir): |
| os.makedirs(settings.object_dir) |
| |
| if not os.path.exists(settings.pch_output_dir): |
| os.makedirs(settings.pch_output_dir) |
| |
| for key in output_file_map: |
| path = output_file_map[key]['object'] |
| if os.path.exists(path): |
| os.unlink(path) |
| |
| output_file_map.setdefault('', {})['swift-dependencies'] = \ |
| os.path.join(tmpdir, module + '.swift.d') |
| |
| output_file_map_path = os.path.join(tmpdir, module + '.json') |
| with open(output_file_map_path, 'w') as output_file_map_file: |
| output_file_map_file.write(json.dumps(output_file_map)) |
| output_file_map_file.flush() |
| |
| extra_args = [] |
| if settings.file_compilation_dir: |
| extra_args.extend([ |
| '-file-compilation-dir', |
| settings.file_compilation_dir, |
| ]) |
| |
| if settings.bridge_header: |
| extra_args.extend([ |
| '-import-objc-header', |
| os.path.abspath(settings.bridge_header), |
| ]) |
| |
| if settings.whole_module_optimization: |
| extra_args.append('-whole-module-optimization') |
| |
| if settings.target: |
| extra_args.extend([ |
| '-target', |
| settings.target, |
| ]) |
| |
| if settings.sdk: |
| extra_args.extend([ |
| '-sdk', |
| os.path.abspath(settings.sdk), |
| ]) |
| |
| if settings.swift_version: |
| extra_args.extend([ |
| '-swift-version', |
| settings.swift_version, |
| ]) |
| |
| if settings.include_dirs: |
| for include_dir in settings.include_dirs: |
| extra_args.append('-I' + include_dir) |
| |
| if settings.system_include_dirs: |
| for system_include_dir in settings.system_include_dirs: |
| extra_args.extend(['-Xcc', '-isystem', '-Xcc', system_include_dir]) |
| |
| if settings.framework_dirs: |
| for framework_dir in settings.framework_dirs: |
| extra_args.extend([ |
| '-F', |
| framework_dir, |
| ]) |
| |
| if settings.system_framework_dirs: |
| for system_framework_dir in settings.system_framework_dirs: |
| extra_args.extend([ |
| '-F', |
| system_framework_dir, |
| ]) |
| |
| if settings.enable_cxx_interop: |
| extra_args.extend([ |
| '-Xfrontend', |
| '-enable-cxx-interop', |
| ]) |
| |
| # The swiftc compiler uses a global module cache that is not robust against |
| # changes in the sub-modules nor against corruption (see crbug.com/1358073). |
| # Force the compiler to store the module cache in a sub-directory of `tmpdir` |
| # to ensure a pristine module cache is used for every compiler invocation. |
| module_cache_path = os.path.join(tmpdir, settings.swiftc_version, |
| 'ModuleCache') |
| |
| # If the generated header is post-processed, generate it to a temporary |
| # location (to avoid having the file appear to suddenly change). |
| if settings.fix_module_imports: |
| header_path = os.path.join(tmpdir, f'{module}.h') |
| else: |
| header_path = settings.header_path |
| |
| process = subprocess.Popen([ |
| settings.swift_toolchain_path + '/usr/bin/swiftc', |
| '-parse-as-library', |
| '-module-name', |
| module, |
| '-module-cache-path', |
| module_cache_path, |
| '-emit-object', |
| '-emit-dependencies', |
| '-emit-module', |
| '-emit-module-path', |
| settings.module_path, |
| '-emit-objc-header', |
| '-emit-objc-header-path', |
| header_path, |
| '-output-file-map', |
| output_file_map_path, |
| '-pch-output-dir', |
| os.path.abspath(settings.pch_output_dir), |
| ] + extra_args + extras + sources) |
| |
| process.communicate() |
| if process.returncode: |
| sys.exit(process.returncode) |
| |
| if settings.fix_module_imports: |
| fix_module_imports(header_path, settings.header_path) |
| |
| # The swiftc compiler generates depfile that uses absolute paths, but |
| # ninja requires paths in depfiles to be identical to paths used in |
| # the build.ninja files. |
| # |
| # Since gn generates paths relative to the build directory for all paths |
| # below the repository checkout, we need to convert those to relative |
| # paths. |
| # |
| # See https://crbug.com/1287114 for build failure that happen when the |
| # paths in the depfile are kept absolute. |
| out_dir = os.getcwd() + os.path.sep |
| src_dir = os.path.abspath(settings.root_dir) + os.path.sep |
| |
| depfile_content = dict() |
| for key in output_file_map: |
| |
| # When whole module optimisation is disabled, there will be an entry |
| # with an empty string as the key and only ('swift-dependencies') as |
| # keys in the value dictionary. This is expected, so skip entry that |
| # do not include 'dependencies' in their keys. |
| depencency_file_path = output_file_map[key].get('dependencies') |
| if not depencency_file_path: |
| continue |
| |
| for line in open(depencency_file_path): |
| output, inputs = line.split(' : ', 2) |
| _, ext = os.path.splitext(output) |
| if ext == '.o': |
| key = output |
| else: |
| key = os.path.splitext(settings.module_path)[0] + ext |
| if key not in depfile_content: |
| depfile_content[key] = set() |
| for path in inputs.split(): |
| if path.startswith(src_dir) or path.startswith(out_dir): |
| path = os.path.relpath(path, out_dir) |
| depfile_content[key].add(path) |
| |
| with open(settings.depfile, 'w') as depfile: |
| keys = sorted(depfile_content.keys()) |
| for key in sorted(keys): |
| depfile.write('%s : %s\n' % (key, ' '.join(sorted(depfile_content[key])))) |
| |
| |
| def main(args): |
| parser = argparse.ArgumentParser(add_help=False) |
| parser.add_argument('-module-name', help='name of the Swift module') |
| parser.add_argument('-include', |
| '-I', |
| action='append', |
| dest='include_dirs', |
| help='add directory to header search path') |
| parser.add_argument('-isystem', |
| action='append', |
| dest='system_include_dirs', |
| help='add directory to system header search path') |
| parser.add_argument('sources', nargs='+', help='Swift source file to compile') |
| parser.add_argument('-whole-module-optimization', |
| action='store_true', |
| help='enable whole module optimization') |
| parser.add_argument('-object-dir', |
| help='path to the generated object files directory') |
| parser.add_argument('-pch-output-dir', |
| help='path to directory where .pch files are saved') |
| parser.add_argument('-module-path', help='path to the generated module file') |
| parser.add_argument('-header-path', help='path to the generated header file') |
| parser.add_argument('-bridge-header', |
| help='path to the Objective-C bridge header') |
| parser.add_argument('-depfile', help='path to the generated depfile') |
| parser.add_argument('-swift-version', |
| help='version of Swift language to support') |
| parser.add_argument('-target', |
| action='store', |
| help='generate code for the given target <triple>') |
| parser.add_argument('-sdk', action='store', help='compile against sdk') |
| parser.add_argument('-F', |
| dest='framework_dirs', |
| action='append', |
| help='add dir to framework search path') |
| parser.add_argument('-Fsystem', |
| '-iframework', |
| dest='system_framework_dirs', |
| action='append', |
| help='add dir to system framework search path') |
| parser.add_argument('-root-dir', |
| dest='root_dir', |
| action='store', |
| required=True, |
| help='path to the root of the repository') |
| parser.add_argument('-swift-toolchain-path', |
| default='', |
| action='store', |
| dest='swift_toolchain_path', |
| help='path to the root of the Swift toolchain') |
| parser.add_argument('-file-compilation-dir', |
| default='', |
| action='store', |
| help='compilation directory to embed in the debug info') |
| parser.add_argument('-enable-cxx-interop', |
| dest='enable_cxx_interop', |
| action='store_true', |
| help='allow importing C++ modules into Swift') |
| parser.add_argument('-fix-module-imports', |
| action='store_true', |
| help='enable hack to fix module imports') |
| parser.add_argument('-swiftc-version', |
| default='', |
| action='store', |
| help='version of swiftc compiler') |
| parser.add_argument('-xcode-version', |
| default='', |
| action='store', |
| help='version of xcode') |
| |
| parsed, extras = parser.parse_known_args(args) |
| with tempfile.TemporaryDirectory() as tmpdir: |
| compile_module(parsed.module_name, parsed.sources, parsed, extras, tmpdir) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |