blob: 50b555ad15fea4dfe7fd0323bdcde6e189a255b5 [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2020 The ChromiumOS Authors
# 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"]
}
}
}
"""
import argparse
import json
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:]))