| # Copyright 2024 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 shutil |
| import tempfile |
| |
| TARGET_CPU_MAPPING = { |
| 'x64': 'x86_64', |
| 'arm64': 'arm64', |
| } |
| |
| METADATA_FILES = ('extract.actionsdata', 'version.json') |
| |
| |
| def read_json(path): |
| """Reads JSON file at `path`.""" |
| with open(path, encoding='utf8') as stream: |
| return json.load(stream) |
| |
| |
| def add_argument(parser, name, help, required=True): |
| """Add argument --{name} to `parser` with description `help`.""" |
| parser.add_argument(f'--{name}', required=required, help=help) |
| |
| |
| def extract_metadata(parsed, module_name, swift_files, const_files): |
| """ |
| Extracts metadata for `module_name` according to `parsed`. |
| |
| If the extraction fails or no metadata is generated, terminate the script |
| with an error (after printing the command stdout/stderr to stderr). |
| """ |
| |
| metadata_dir = os.path.join(parsed.output, 'Metadata.appintents') |
| if os.path.exists(metadata_dir): |
| shutil.rmtree(metadata_dir) |
| |
| target_cpu = TARGET_CPU_MAPPING[parsed.target_cpu] |
| target_triple = f'{target_cpu}-apple-ios{parsed.deployment_target}' |
| if parsed.target_environment == 'simulator': |
| target_triple += '-simulator' |
| |
| command = [ |
| os.path.join(parsed.toolchain_dir, 'usr/bin/appintentsmetadataprocessor'), |
| '--toolchain-dir', |
| parsed.toolchain_dir, |
| '--sdk-root', |
| parsed.sdk_root, |
| '--deployment-target', |
| parsed.deployment_target, |
| '--target-triple', |
| target_triple, |
| '--module-name', |
| module_name, |
| '--output', |
| parsed.output, |
| '--binary-file', |
| parsed.binary_file, |
| '--compile-time-extraction', |
| ] |
| |
| inputs = set() |
| inputs.add(parsed.binary_file) |
| |
| for swift_file in swift_files: |
| inputs.add(swift_file) |
| command.extend(('--source-files', swift_file)) |
| |
| for const_file in const_files: |
| inputs.add(const_file) |
| command.extend(('--swift-const-vals', const_file)) |
| |
| if parsed.xcode_version is not None: |
| command.extend(('--xcode-version', parsed.xcode_version)) |
| |
| process = subprocess.Popen(command, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| (stdout, stderr) = process.communicate() |
| |
| if process.returncode: |
| sys.stderr.write(stdout.decode('utf8')) |
| sys.stderr.write(stderr.decode('utf8')) |
| return process.returncode |
| |
| # Force failure if the tool extracted no data. This is because gn does |
| # not support optional outputs and thus it would consider the build as |
| # dirty if the output is missing. |
| if not os.path.exists(metadata_dir): |
| sys.stderr.write(f'error: no metadata generated for {module_name}\n') |
| sys.stderr.write(stdout.decode('utf8')) |
| sys.stderr.write(stderr.decode('utf8')) |
| return 1 # failure |
| |
| output_files = METADATA_FILES |
| with open(parsed.depfile, 'w', encoding='utf8') as depfile: |
| for output in output_files: |
| depfile.write(f'{metadata_dir}/{output}:') |
| for item in sorted(inputs): |
| depfile.write(f' {item}') |
| depfile.write('\n') |
| |
| return 0 # success |
| |
| |
| def main(args): |
| parser = argparse.ArgumentParser() |
| |
| add_argument(parser, 'output', 'path to the output directory') |
| add_argument(parser, 'depfile', 'path to the output depfile') |
| add_argument(parser, 'toolchain-dir', 'path to the toolchain directory') |
| add_argument(parser, 'sdk-root', 'path to the SDK root directory') |
| add_argument(parser, 'target-cpu', 'target cpu architecture') |
| add_argument(parser, 'target-environment', 'target environment') |
| add_argument(parser, 'deployment-target', 'deployment target version') |
| add_argument(parser, 'binary-file', 'path to the binary to process') |
| add_argument(parser, 'module-info-path', 'path to the module info JSON file') |
| add_argument(parser, 'xcode-version', 'version of Xcode', required=False) |
| |
| parsed = parser.parse_args(args) |
| |
| module_info = read_json(parsed.module_info_path) |
| return extract_metadata( |
| parsed, # |
| module_info['module_name'], |
| module_info['swift_files'], |
| module_info['const_files']) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |