| #!/usr/bin/env python3 |
| import argparse |
| import os |
| import re |
| import shlex |
| import shutil |
| import subprocess |
| import sys |
| |
| # Preprocesses wtf/Platform.h with clang, extracts macro definitions, and saves |
| # the platform flags to a Swift-style response file containing -D arguments. |
| # |
| # Three modes: |
| # |
| # Xcode build phase (default, no args): driven by Xcode env vars |
| # ($SCRIPT_OUTPUT_FILE_0, $ARCHS, $DERIVED_FILES_DIR, ...). Iterates over |
| # $ARCHS and writes one resp per arch. |
| # |
| # cmake (--cmake): driven by argparse. Writes a single resp file containing |
| # both the platform -D flags and -Xcc -DNAME=VALUE seeds for cmakeconfig.h |
| # so the Swift clang importer's view of ENABLE()/HAVE() macros agrees with |
| # regular C++ compilation. cmake builds the clang preprocess command itself |
| # (since it owns the configured compiler, sysroot, target triple and |
| # include paths) and passes it after `--`. |
| # |
| # cmake feature defines (--print-feature-defines): writes the truthy platform |
| # define names to --output (or stdout) for the generators' --defines-file. |
| |
| DEFINE_RE = re.compile(r'^#define (\w+) (.+)', re.MULTILINE) |
| PLATFORM_PREFIX_RE = re.compile(r'^(HAVE_|USE_|ENABLE_|WTF_PLATFORM|ASSERT_)') |
| |
| |
| def platform_defines_from_dM(stdout): |
| """Parse `clang -E -dM` output and return the names of the truthy |
| HAVE_/USE_/ENABLE_/WTF_PLATFORM_/ASSERT_ macros. Resolves simple macro |
| indirection: clang -dM emits macros alphabetically rather than |
| topologically, so a macro may reference another we have not yet seen; |
| iterate to a fixed point with a bound to avoid recursive definitions |
| looping forever.""" |
| definitions = {} |
| for m in re.finditer(DEFINE_RE, stdout): |
| name = m.group(1) |
| value = m.group(2) |
| if value == '1': |
| definitions[name] = 1 |
| elif value == '0': |
| definitions[name] = 0 |
| else: |
| definitions[name] = value |
| for _ in range(10): |
| changed = False |
| for name, value in definitions.items(): |
| if type(value) == str and value in definitions: |
| definitions[name] = definitions[value] |
| changed = True |
| if not changed: |
| break |
| return [ |
| name for name, value in definitions.items() |
| if value == 1 and PLATFORM_PREFIX_RE.match(name) |
| ] |
| |
| |
| def run_xcode_mode(): |
| # For build settings which may be undefined, behave like a shell and implicitly |
| # fall back to the empty string. |
| os.environ.setdefault('FRAMEWORK_SEARCH_PATHS', '') |
| os.environ.setdefault('HEADER_SEARCH_PATHS', '') |
| os.environ.setdefault('SYSTEM_FRAMEWORK_SEARCH_PATHS', '') |
| os.environ.setdefault('SYSTEM_HEADER_SEARCH_PATHS', '') |
| os.environ.setdefault('GCC_PREPROCESSOR_DEFINITIONS', '') |
| |
| framework_search_paths = shlex.split( |
| '{BUILT_PRODUCTS_DIR} {FRAMEWORK_SEARCH_PATHS}'.format_map(os.environ)) |
| header_search_paths = shlex.split( |
| '{BUILT_PRODUCTS_DIR} {HEADER_SEARCH_PATHS}'.format_map(os.environ)) |
| system_framework_search_paths = shlex.split( |
| '{SYSTEM_FRAMEWORK_SEARCH_PATHS}'.format_map(os.environ)) |
| system_header_search_paths = shlex.split( |
| '{SYSTEM_HEADER_SEARCH_PATHS}'.format_map(os.environ)) |
| preprocessor_definitions = shlex.split( |
| '__WK_GENERATING_PLATFORM_ARGS__ RELEASE_WITHOUT_OPTIMIZATIONS ' |
| '{GCC_PREPROCESSOR_DEFINITIONS}'.format_map(os.environ)) |
| sanitizer_flags = [] |
| if os.environ.get('ENABLE_ADDRESS_SANITIZER') == 'YES': |
| sanitizer_flags.append('-fsanitize=address') |
| |
| archs = shlex.split(os.environ['ARCHS']) |
| if 'SWIFT_MODULE_ONLY_ARCHS' in os.environ: |
| archs.extend(shlex.split(os.environ['SWIFT_MODULE_ONLY_ARCHS'])) |
| |
| output_dir = os.path.dirname(os.environ['SCRIPT_OUTPUT_FILE_0']) |
| |
| combined_depfile_fd = open( |
| '{DERIVED_FILES_DIR}/generate-platform-args.d'.format_map(os.environ), 'w') |
| |
| # This script is run as a build phase, which Xcode runs once with |
| # $arch=undefined, so we have to handle multi-arch builds manually. We generate |
| # argument lists for all active architectures, and the build script phase |
| # declares all known output paths as outputs. |
| # |
| # FIXME: Computing search path arguments is duplicated by migrate-header-rule |
| # in WebKitLegacy. We could share this implementation and generate |
| # bash-compatible variables for these use cases as well. |
| |
| with open(f'{output_dir}/rdar150228472.swift', 'w') as fd: |
| fd.write('// generate-platform-args: empty file to work around dependency ' |
| 'bug in rdar://150228472\n') |
| |
| for arch in archs: |
| output_name = f'platform-enabled-swift-args.{arch}.resp' |
| output_file = f'{output_dir}/{output_name}' |
| depfile = f'{{DERIVED_FILES_DIR}}/{output_name}.d'.format_map(os.environ) |
| target_triple = (f'{arch}-' |
| '{LLVM_TARGET_TRIPLE_VENDOR}-{LLVM_TARGET_TRIPLE_OS_VERSION}').format_map(os.environ) |
| target_triple += os.environ.get('LLVM_TARGET_TRIPLE_SUFFIX', '') |
| |
| feature_and_platform_defines = subprocess.check_output( |
| ('xcrun', 'clang', '-x', 'c++', '-std={CLANG_CXX_LANGUAGE_STANDARD}'.format_map(os.environ), |
| '-target', target_triple, '-E', '-P', '-dM', '-MD', '-MF', depfile, '-MT', output_name, |
| *(sanitizer_flags), |
| *(f'-D{token}' for token in preprocessor_definitions), |
| *(arg for path in framework_search_paths for arg in ('-F', path)), |
| *(arg for path in header_search_paths for arg in ('-I', path)), |
| *(arg for path in system_framework_search_paths for arg in ('-iframework', path)), |
| *(arg for path in system_header_search_paths for arg in ('-isystem', path)), |
| '-include', 'wtf/Platform.h', '/dev/zero'), text=True) |
| |
| # Concatenate all the depfiles, in case a header is only imported on specific architectures. |
| shutil.copyfileobj(open(depfile), combined_depfile_fd) |
| |
| names = platform_defines_from_dM(feature_and_platform_defines) |
| swift_args = '\n'.join(f'-D{name}' for name in names) |
| print(f'{output_file}:\t', swift_args.replace('\n', ' ')) |
| open(output_file, 'w').write(swift_args) |
| |
| |
| def run_clang_command(command): |
| """Run the clang preprocess command that follows `--` and return its stdout.""" |
| cmd = command |
| if cmd and cmd[0] == '--': |
| cmd = cmd[1:] |
| if not cmd: |
| sys.exit('clang command is required after --') |
| result = subprocess.run(cmd, capture_output=True, text=True) |
| if result.returncode != 0: |
| sys.stderr.write(result.stderr) |
| sys.exit(result.returncode) |
| return result.stdout |
| |
| |
| def run_cmake_mode(args): |
| swift_defines = platform_defines_from_dM(run_clang_command(args.command)) |
| |
| cmake_seeds = [] |
| with open(args.cmakeconfig) as f: |
| for line in f: |
| m = DEFINE_RE.match(line.strip()) |
| if m: |
| cmake_seeds.append((m.group(1), m.group(2))) |
| |
| out_lines = [] |
| for name in swift_defines: |
| out_lines.append('-D' + name) |
| for name, value in cmake_seeds: |
| out_lines.append('-Xcc') |
| out_lines.append(f'-D{name}={value}') |
| write_if_changed(args.output, '\n'.join(out_lines) + '\n') |
| |
| |
| def write_if_changed(path, content): |
| """Write only when the content differs, so an unchanged rerun doesn't bump the |
| mtime and rebuild everything that depends on the file.""" |
| try: |
| with open(path) as f: |
| if f.read() == content: |
| return |
| except FileNotFoundError: |
| pass |
| with open(path, 'w') as f: |
| f.write(content) |
| |
| |
| def run_print_feature_defines_mode(args): |
| names = platform_defines_from_dM(run_clang_command(args.command)) |
| if args.output: |
| write_if_changed(args.output, '\n'.join(names) + '\n') |
| else: |
| for name in names: |
| print(name) |
| |
| |
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument('--cmake', action='store_true', |
| help='Run in cmake mode (argparse-driven; otherwise Xcode env-driven).') |
| ap.add_argument('--print-feature-defines', action='store_true', |
| help='cmake mode: write truthy platform define names to ' |
| '--output (or stdout) for the generators.') |
| ap.add_argument('--output', |
| help='cmake mode: output path (--cmake resp or ' |
| '--print-feature-defines names).') |
| ap.add_argument('--cmakeconfig', |
| help='cmake mode: path to cmakeconfig.h to seed the clang importer.') |
| ap.add_argument('command', nargs=argparse.REMAINDER, |
| help='cmake mode: clang preprocess command (preceded by --).') |
| args = ap.parse_args() |
| |
| if args.print_feature_defines: |
| run_print_feature_defines_mode(args) |
| elif args.cmake: |
| if not args.output or not args.cmakeconfig: |
| ap.error('--cmake requires --output and --cmakeconfig') |
| run_cmake_mode(args) |
| else: |
| run_xcode_mode() |
| |
| |
| if __name__ == '__main__': |
| main() |