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