# 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:]))