1# Copyright 2024 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import argparse 6import json 7import os 8import subprocess 9import sys 10import shutil 11import tempfile 12 13TARGET_CPU_MAPPING = { 14 'x64': 'x86_64', 15 'arm64': 'arm64', 16} 17 18METADATA_FILES = ('extract.actionsdata', 'version.json') 19 20 21def read_json(path): 22 """Reads JSON file at `path`.""" 23 with open(path, encoding='utf8') as stream: 24 return json.load(stream) 25 26 27def add_argument(parser, name, help, required=True): 28 """Add argument --{name} to `parser` with description `help`.""" 29 parser.add_argument(f'--{name}', required=required, help=help) 30 31 32def extract_metadata(parsed, module_name, swift_files, const_files): 33 """ 34 Extracts metadata for `module_name` according to `parsed`. 35 36 If the extraction fails or no metadata is generated, terminate the script 37 with an error (after printing the command stdout/stderr to stderr). 38 """ 39 40 metadata_dir = os.path.join(parsed.output, 'Metadata.appintents') 41 if os.path.exists(metadata_dir): 42 shutil.rmtree(metadata_dir) 43 44 target_cpu = TARGET_CPU_MAPPING[parsed.target_cpu] 45 target_triple = f'{target_cpu}-apple-ios{parsed.deployment_target}' 46 if parsed.target_environment == 'simulator': 47 target_triple += '-simulator' 48 49 command = [ 50 os.path.join(parsed.toolchain_dir, 'usr/bin/appintentsmetadataprocessor'), 51 '--toolchain-dir', 52 parsed.toolchain_dir, 53 '--sdk-root', 54 parsed.sdk_root, 55 '--deployment-target', 56 parsed.deployment_target, 57 '--target-triple', 58 target_triple, 59 '--module-name', 60 module_name, 61 '--output', 62 parsed.output, 63 '--binary-file', 64 parsed.binary_file, 65 '--compile-time-extraction', 66 ] 67 68 inputs = set() 69 inputs.add(parsed.binary_file) 70 71 for swift_file in swift_files: 72 inputs.add(swift_file) 73 command.extend(('--source-files', swift_file)) 74 75 for const_file in const_files: 76 inputs.add(const_file) 77 command.extend(('--swift-const-vals', const_file)) 78 79 if parsed.xcode_version is not None: 80 command.extend(('--xcode-version', parsed.xcode_version)) 81 82 process = subprocess.Popen(command, 83 stdout=subprocess.PIPE, 84 stderr=subprocess.PIPE) 85 (stdout, stderr) = process.communicate() 86 87 if process.returncode: 88 sys.stderr.write(stdout.decode('utf8')) 89 sys.stderr.write(stderr.decode('utf8')) 90 return process.returncode 91 92 # Force failure if the tool extracted no data. This is because gn does 93 # not support optional outputs and thus it would consider the build as 94 # dirty if the output is missing. 95 if not os.path.exists(metadata_dir): 96 sys.stderr.write(f'error: no metadata generated for {module_name}\n') 97 sys.stderr.write(stdout.decode('utf8')) 98 sys.stderr.write(stderr.decode('utf8')) 99 return 1 # failure 100 101 output_files = METADATA_FILES 102 with open(parsed.depfile, 'w', encoding='utf8') as depfile: 103 for output in output_files: 104 depfile.write(f'{metadata_dir}/{output}:') 105 for item in sorted(inputs): 106 depfile.write(f' {item}') 107 depfile.write('\n') 108 109 return 0 # success 110 111 112def main(args): 113 parser = argparse.ArgumentParser() 114 115 add_argument(parser, 'output', 'path to the output directory') 116 add_argument(parser, 'depfile', 'path to the output depfile') 117 add_argument(parser, 'toolchain-dir', 'path to the toolchain directory') 118 add_argument(parser, 'sdk-root', 'path to the SDK root directory') 119 add_argument(parser, 'target-cpu', 'target cpu architecture') 120 add_argument(parser, 'target-environment', 'target environment') 121 add_argument(parser, 'deployment-target', 'deployment target version') 122 add_argument(parser, 'binary-file', 'path to the binary to process') 123 add_argument(parser, 'module-info-path', 'path to the module info JSON file') 124 add_argument(parser, 'xcode-version', 'version of Xcode', required=False) 125 126 parsed = parser.parse_args(args) 127 128 module_info = read_json(parsed.module_info_path) 129 return extract_metadata( 130 parsed, # 131 module_info['module_name'], 132 module_info['swift_files'], 133 module_info['const_files']) 134 135 136if __name__ == '__main__': 137 sys.exit(main(sys.argv[1:])) 138