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