xref: /aosp_15_r20/external/angle/build/config/ios/extract_metadata.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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