blob: 60d331c5b0d1eacf675de2dc3c49e3af726213a6 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Produces a JSON object of `gn desc`'s output for each given arch.
A full Chromium checkout is required in order to run this script.
The result is of the form:
{
"arch1": {
"//gn:target": {
'configs": ["bar"],
"sources": ["foo"]
}
}
}
"""
from __future__ import print_function
import argparse
import json
# pylint: disable=cros-logging-import
import logging
import os
import subprocess
import sys
import tempfile
def _find_chromium_root(search_from):
"""Finds the chromium root directory from `search_from`."""
current = search_from
while current != '/':
if os.path.isfile(os.path.join(current, '.gclient')):
return current
current = os.path.dirname(current)
raise ValueError(
"%s doesn't appear to be a Chromium subdirectory" % search_from)
def _create_gn_args_for(arch):
"""Creates a `gn args` listing for the given architecture."""
# FIXME(gbiv): is_chromeos_device = True would be nice to support, as well.
# Requires playing nicely with SimpleChrome though, and this should be "close
# enough" for now.
return '\n'.join((
'target_os = "chromeos"',
'target_cpu = "%s"' % arch,
'is_official_build = true',
'is_chrome_branded = true',
))
def _parse_gn_desc_output(output):
"""Parses the output of `gn desc --format=json`.
Args:
output: a seekable file containing the JSON output of `gn desc`.
Returns:
A tuple of (warnings, gn_desc_json).
"""
warnings = []
desc_json = None
while True:
start_pos = output.tell()
next_line = next(output, None)
if next_line is None:
raise ValueError('No JSON found in the given gn file')
if next_line.lstrip().startswith('{'):
output.seek(start_pos)
desc_json = json.load(output)
break
warnings.append(next_line)
return ''.join(warnings).strip(), desc_json
def _run_gn_desc(in_dir, gn_args):
logging.info('Running `gn gen`...')
subprocess.check_call(['gn', 'gen', '.', '--args=' + gn_args], cwd=in_dir)
logging.info('Running `gn desc`...')
with tempfile.TemporaryFile(mode='r+', encoding='utf-8') as f:
gn_command = ['gn', 'desc', '--format=json', '.', '//*:*']
exit_code = subprocess.call(gn_command, stdout=f, cwd=in_dir)
f.seek(0)
if exit_code:
logging.error('gn failed; stdout:\n%s', f.read())
raise subprocess.CalledProcessError(exit_code, gn_command)
warnings, result = _parse_gn_desc_output(f)
if warnings:
logging.warning('Encountered warning(s) running `gn desc`:\n%s', warnings)
return result
def _fix_result(rename_out, out_dir, chromium_root, gn_desc):
"""Performs postprocessing on `gn desc` JSON."""
result = {}
rel_out = '//' + os.path.relpath(out_dir, os.path.join(chromium_root, 'src'))
rename_out = rename_out if rename_out.endswith('/') else rename_out + '/'
def fix_source_file(f):
if not f.startswith(rel_out):
return f
return rename_out + f[len(rel_out) + 1:]
for target, info in gn_desc.items():
sources = info.get('sources')
configs = info.get('configs')
if not sources or not configs:
continue
result[target] = {
'configs': configs,
'sources': [fix_source_file(f) for f in sources],
}
return result
def main(args):
known_arches = [
'arm',
'arm64',
'x64',
'x86',
]
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'arch',
nargs='+',
help='Architecture(s) to fetch `gn desc`s for. '
'Supported ones are %s' % known_arches)
parser.add_argument(
'--output', required=True, help='File to write results to.')
parser.add_argument(
'--chromium_out_dir',
required=True,
help='Chromium out/ directory for us to use. This directory will '
'be clobbered by this script.')
parser.add_argument(
'--rename_out',
default='//out',
help='Directory to rename files in --chromium_out_dir to. '
'Default: %(default)s')
opts = parser.parse_args(args)
logging.basicConfig(
format='%(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: %(message)s',
level=logging.INFO,
)
arches = opts.arch
rename_out = opts.rename_out
for arch in arches:
if arch not in known_arches:
parser.error(
'unknown architecture: %s; try one of %s' % (arch, known_arches))
results_file = os.path.realpath(opts.output)
out_dir = os.path.realpath(opts.chromium_out_dir)
chromium_root = _find_chromium_root(out_dir)
os.makedirs(out_dir, exist_ok=True)
results = {}
for arch in arches:
logging.info('Getting `gn` desc for %s...', arch)
results[arch] = _fix_result(
rename_out, out_dir, chromium_root,
_run_gn_desc(
in_dir=out_dir,
gn_args=_create_gn_args_for(arch),
))
os.makedirs(os.path.dirname(results_file), exist_ok=True)
results_intermed = results_file + '.tmp'
with open(results_intermed, 'w', encoding='utf-8') as f:
json.dump(results, f)
os.rename(results_intermed, results_file)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))