• Home
  • 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
9A full Chromium checkout is required in order to run this script.
10
11The result is of the form:
12{
13  "arch1": {
14    "//gn:target": {
15      'configs": ["bar"],
16      "sources": ["foo"]
17    }
18  }
19}
20"""
21
22from __future__ import print_function
23
24import argparse
25import json
26# pylint: disable=cros-logging-import
27import logging
28import os
29import subprocess
30import sys
31import tempfile
32
33
34def _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
45def _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
58def _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
85def _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
104def _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
130def 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
195if __name__ == '__main__':
196  sys.exit(main(sys.argv[1:]))
197