• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 # Copyright 2020 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6 
7 """Produces a JSON object of `gn desc`'s output for each given arch.
8 
9 A full Chromium checkout is required in order to run this script.
10 
11 The result is of the form:
12 {
13   "arch1": {
14     "//gn:target": {
15       'configs": ["bar"],
16       "sources": ["foo"]
17     }
18   }
19 }
20 """
21 
22 from __future__ import print_function
23 
24 import argparse
25 import json
26 # pylint: disable=cros-logging-import
27 import logging
28 import os
29 import subprocess
30 import sys
31 import tempfile
32 
33 
34 def _find_chromium_root(search_from):
35   """Finds the chromium root directory from `search_from`."""
36   current = search_from
37   while current != '/':
38     if os.path.isfile(os.path.join(current, '.gclient')):
39       return current
40     current = os.path.dirname(current)
41   raise ValueError(
42       "%s doesn't appear to be a Chromium subdirectory" % search_from)
43 
44 
45 def _create_gn_args_for(arch):
46   """Creates a `gn args` listing for the given architecture."""
47   # FIXME(gbiv): is_chromeos_device = True would be nice to support, as well.
48   # Requires playing nicely with SimpleChrome though, and this should be "close
49   # enough" for now.
50   return '\n'.join((
51       'target_os = "chromeos"',
52       'target_cpu = "%s"' % arch,
53       'is_official_build = true',
54       'is_chrome_branded = true',
55   ))
56 
57 
58 def _parse_gn_desc_output(output):
59   """Parses the output of `gn desc --format=json`.
60 
61   Args:
62     output: a seekable file containing the JSON output of `gn desc`.
63 
64   Returns:
65     A tuple of (warnings, gn_desc_json).
66   """
67   warnings = []
68   desc_json = None
69   while True:
70     start_pos = output.tell()
71     next_line = next(output, None)
72     if next_line is None:
73       raise ValueError('No JSON found in the given gn file')
74 
75     if next_line.lstrip().startswith('{'):
76       output.seek(start_pos)
77       desc_json = json.load(output)
78       break
79 
80     warnings.append(next_line)
81 
82   return ''.join(warnings).strip(), desc_json
83 
84 
85 def _run_gn_desc(in_dir, gn_args):
86   logging.info('Running `gn gen`...')
87   subprocess.check_call(['gn', 'gen', '.', '--args=' + gn_args], cwd=in_dir)
88 
89   logging.info('Running `gn desc`...')
90   with tempfile.TemporaryFile(mode='r+', encoding='utf-8') as f:
91     gn_command = ['gn', 'desc', '--format=json', '.', '//*:*']
92     exit_code = subprocess.call(gn_command, stdout=f, cwd=in_dir)
93     f.seek(0)
94     if exit_code:
95       logging.error('gn failed; stdout:\n%s', f.read())
96       raise subprocess.CalledProcessError(exit_code, gn_command)
97     warnings, result = _parse_gn_desc_output(f)
98 
99   if warnings:
100     logging.warning('Encountered warning(s) running `gn desc`:\n%s', warnings)
101   return result
102 
103 
104 def _fix_result(rename_out, out_dir, chromium_root, gn_desc):
105   """Performs postprocessing on `gn desc` JSON."""
106   result = {}
107 
108   rel_out = '//' + os.path.relpath(out_dir, os.path.join(chromium_root, 'src'))
109   rename_out = rename_out if rename_out.endswith('/') else rename_out + '/'
110 
111   def fix_source_file(f):
112     if not f.startswith(rel_out):
113       return f
114     return rename_out + f[len(rel_out) + 1:]
115 
116   for target, info in gn_desc.items():
117     sources = info.get('sources')
118     configs = info.get('configs')
119     if not sources or not configs:
120       continue
121 
122     result[target] = {
123         'configs': configs,
124         'sources': [fix_source_file(f) for f in sources],
125     }
126 
127   return result
128 
129 
130 def main(args):
131   known_arches = [
132       'arm',
133       'arm64',
134       'x64',
135       'x86',
136   ]
137 
138   parser = argparse.ArgumentParser(
139       description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
140   parser.add_argument(
141       'arch',
142       nargs='+',
143       help='Architecture(s) to fetch `gn desc`s for. '
144       'Supported ones are %s' % known_arches)
145   parser.add_argument(
146       '--output', required=True, help='File to write results to.')
147   parser.add_argument(
148       '--chromium_out_dir',
149       required=True,
150       help='Chromium out/ directory for us to use. This directory will '
151       'be clobbered by this script.')
152   parser.add_argument(
153       '--rename_out',
154       default='//out',
155       help='Directory to rename files in --chromium_out_dir to. '
156       'Default: %(default)s')
157   opts = parser.parse_args(args)
158 
159   logging.basicConfig(
160       format='%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: %(message)s',
161       level=logging.INFO,
162   )
163 
164   arches = opts.arch
165   rename_out = opts.rename_out
166   for arch in arches:
167     if arch not in known_arches:
168       parser.error(
169           'unknown architecture: %s; try one of %s' % (arch, known_arches))
170 
171   results_file = os.path.realpath(opts.output)
172   out_dir = os.path.realpath(opts.chromium_out_dir)
173   chromium_root = _find_chromium_root(out_dir)
174 
175   os.makedirs(out_dir, exist_ok=True)
176   results = {}
177   for arch in arches:
178     logging.info('Getting `gn` desc for %s...', arch)
179 
180     results[arch] = _fix_result(
181         rename_out, out_dir, chromium_root,
182         _run_gn_desc(
183             in_dir=out_dir,
184             gn_args=_create_gn_args_for(arch),
185         ))
186 
187   os.makedirs(os.path.dirname(results_file), exist_ok=True)
188 
189   results_intermed = results_file + '.tmp'
190   with open(results_intermed, 'w', encoding='utf-8') as f:
191     json.dump(results, f)
192   os.rename(results_intermed, results_file)
193 
194 
195 if __name__ == '__main__':
196   sys.exit(main(sys.argv[1:]))
197