1#!/usr/bin/python3 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import contextlib 19import json 20import os 21 22 23def _parser(): 24 """Return the argument parser""" 25 # Top-level parser 26 parser = argparse.ArgumentParser(prog=".inner_build") 27 28 # --out-dir is always provided. 29 parser.add_argument("--out_dir", 30 action="store", 31 required=True, 32 help="output directory path") 33 34 sub = parser.add_subparsers(required=True, 35 dest="command", 36 help="subcommands") 37 38 # inner_build describe command 39 describe = sub.add_parser( 40 "describe", 41 help="describe the capabilities of this inner tree's build system") 42 describe.add_argument('--input_json', 43 required=True, 44 help="The json encoded request information.") 45 describe.add_argument('--output_json', 46 required=True, 47 help="The json encoded description.") 48 49 # create the parser for the "export_api_contributions" command. 50 export = sub.add_parser( 51 "export_api_contributions", 52 help="export the API contributions of this inner tree") 53 export.add_argument( 54 "--api_domain", 55 action="append", 56 required=True, 57 help="which API domains are to be built in this inner tree") 58 # This is not needed now that we have nsjail, since it launches inner_build 59 # with cwd at the top of the inner tree. 60 export.add_argument("--inner_tree", 61 action="store", 62 required=True, 63 help="path to the inner tree") 64 65 # create the parser for the "analyze" command. 66 analyze = sub.add_parser("analyze", 67 help="main build analysis for this inner tree") 68 # TODO: do we need this, or does it just need to be mapped in nsjail? 69 analyze.add_argument("--api_surfaces_dir", 70 action="append", 71 required=True, 72 help="the api_surfaces directory path") 73 analyze.add_argument("--generate_ninja", 74 action="store_true", 75 help="generate ninja rules") 76 # This is not needed now that we have nsjail, since it launches inner_build 77 # with cwd at the top of the inner tree. 78 analyze.add_argument("--inner_tree", 79 action="store", 80 required=True, 81 help="path to the inner tree") 82 83 return parser 84 85 86class Commands(object): 87 """Base class for inner_build commands.""" 88 89 valid_commands = ("describe", "export_api_contributions", "analyze") 90 91 def Run(self, argv): 92 """Parse command arguments and call the named subcommand. 93 94 Throws AttributeError if the method for the command wasn't found. 95 """ 96 args = _parser().parse_args(argv[1:]) 97 if args.command not in self.valid_commands: 98 raise Exception(f"invalid command: {args.command}") 99 return getattr(self, args.command)(args) 100 101 def describe(self, args): 102 """Perform the default 'describe' processing.""" 103 104 with open(args.input_json, encoding='iso-8859-1') as f: 105 query = json.load(f) 106 107 # This version of describe() simply replies with the build_domains 108 # requested. If the inner tree can't build the requested build_domain, 109 # then the build will fail later. If the inner tree has knowledge of 110 # what can be built, it should override this method with a method that 111 # returns the appropriate information. 112 # TODO: bazel-only builds will need to figure this out. 113 domain_data = [{"domains": [query.get("build_domains", [])]}] 114 reply = {"version": 0, "domain_data": domain_data} 115 116 filename = args.output_json or os.path.join(args.out_dir, 117 "tree_info.json") 118 os.makedirs(os.path.dirname(filename), exist_ok=True) 119 with open(filename, "w", encoding='iso-8859-1') as f: 120 json.dump(reply, f, indent=4) 121 122 def export_api_contributions(self, args): 123 raise Exception(f"export_api_contributions({args}) not implemented") 124 125 def analyze(self, args): 126 raise Exception(f"analyze({args}) not implemented") 127 128 129@contextlib.contextmanager 130def setenv(**kwargs): 131 """Context to adjust environment.""" 132 old_values = {} 133 delete_vars = set() 134 for k, v in kwargs.items(): 135 # Save the prior state. 136 if k in os.environ: 137 old_values[k] = os.environ[k] 138 else: 139 delete_vars.add(k) 140 141 # Set the new value. 142 if v is None: 143 del os.environ[k] 144 else: 145 os.environ[k] = v 146 try: 147 yield 148 finally: 149 # Restore the old values. 150 for k, v in old_values.items(): 151 os.environ[k] = v 152 for k in delete_vars: 153 del os.environ[k] 154