blob: 727c2e3e572b79895ef6b14e471ac5c6f3330ac5 [file] [log] [blame]
Alex Kleinf4dc4f52018-12-05 13:55:12 -07001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Alex Kleind815ca62020-01-10 12:21:30 -07006"""The Build API entry point."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -07007
8from __future__ import print_function
9
Alex Klein5bcb4d22019-03-21 13:51:54 -060010import os
Mike Frysinger898265b2020-02-10 23:49:12 -050011import sys
Alex Kleind815ca62020-01-10 12:21:30 -070012
Alex Klein69339cc2019-07-22 14:08:35 -060013from chromite.api import api_config as api_config_lib
Alex Klein2008aee2019-08-20 16:25:27 -060014from chromite.api import controller
Alex Kleine191ed62020-02-27 15:59:55 -070015from chromite.api import message_util
Alex Klein146d4772019-06-20 13:48:25 -060016from chromite.api import router as router_lib
Alex Kleind815ca62020-01-10 12:21:30 -070017from chromite.api.gen.chromite.api import build_api_config_pb2
Alex Kleinf4dc4f52018-12-05 13:55:12 -070018from chromite.lib import commandline
Alex Klein2bfacb22019-02-04 11:42:17 -070019from chromite.lib import cros_build_lib
Michael Mortensen3e86c1e2019-11-21 15:51:54 -070020from chromite.lib import cros_logging as logging
21from chromite.lib import tee
Alex Klein00b1f1e2019-02-08 13:53:42 -070022from chromite.utils import matching
Alex Kleinf4dc4f52018-12-05 13:55:12 -070023
Mike Frysinger898265b2020-02-10 23:49:12 -050024assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
25
26
Alex Kleinf4dc4f52018-12-05 13:55:12 -070027def GetParser():
Alex Klein00b1f1e2019-02-08 13:53:42 -070028 """Build the argument parser."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -070029 parser = commandline.ArgumentParser(description=__doc__)
30
Alex Kleind815ca62020-01-10 12:21:30 -070031 parser.add_argument(
Alex Klein2008aee2019-08-20 16:25:27 -060032 'service_method',
Alex Klein2008aee2019-08-20 16:25:27 -060033 help='The "chromite.api.Service/Method" that is being called.')
Alex Kleine191ed62020-02-27 15:59:55 -070034 # Input arguments.
35 input_args = parser.add_mutually_exclusive_group(required=True)
36 input_args.add_argument(
37 '--input-binary',
38 type='path',
39 help='Path to the protobuf binary serialization of the input message.')
40 input_args.add_argument(
Alex Klein2008aee2019-08-20 16:25:27 -060041 '--input-json',
42 type='path',
Alex Kleinf4dc4f52018-12-05 13:55:12 -070043 help='Path to the JSON serialized input argument protobuf message.')
Alex Kleine191ed62020-02-27 15:59:55 -070044 # Output options.
45 parser.add_argument(
46 '--output-binary',
47 type='path',
48 help='The path to which the protobuf binary serialization of the '
49 'response message should be written.')
Alex Kleind815ca62020-01-10 12:21:30 -070050 parser.add_argument(
Alex Klein2008aee2019-08-20 16:25:27 -060051 '--output-json',
52 type='path',
Alex Kleine191ed62020-02-27 15:59:55 -070053 help='The path to which the JSON serialization of the response message '
54 'should be written.')
55 # Config options.
56 config_args = parser.add_mutually_exclusive_group()
57 config_args.add_argument(
58 '--config-binary',
59 type='path',
60 help='The path to the protobuf binary serialization of the Build API '
61 'call configs.')
62 config_args.add_argument(
Alex Kleind815ca62020-01-10 12:21:30 -070063 '--config-json',
64 type='path',
Alex Kleine191ed62020-02-27 15:59:55 -070065 help='The path to the JSON encoded Build API call configs.')
Alex Kleind815ca62020-01-10 12:21:30 -070066 # TODO(crbug.com/1040978): Remove after usages removed.
67 parser.add_argument(
Michael Mortensen3e86c1e2019-11-21 15:51:54 -070068 '--tee-log',
69 type='path',
70 help='The path to which stdout and stderr should be teed to.')
Alex Klein2008aee2019-08-20 16:25:27 -060071
Alex Kleinf4dc4f52018-12-05 13:55:12 -070072 return parser
73
74
Alex Klein00b1f1e2019-02-08 13:53:42 -070075def _ParseArgs(argv, router):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070076 """Parse and validate arguments."""
77 parser = GetParser()
Alex Klein7cc434f2019-12-17 14:58:57 -070078 opts, unknown = parser.parse_known_args(
79 argv, namespace=commandline.ArgumentNamespace())
80 parser.DoPostParseSetup(opts, unknown)
81
82 if unknown:
83 logging.warning('Unknown args ignored: %s', ' '.join(unknown))
Alex Kleinf4dc4f52018-12-05 13:55:12 -070084
Alex Klein00b1f1e2019-02-08 13:53:42 -070085 methods = router.ListMethods()
George Engelbrechtd3de8df2019-09-04 18:15:05 -060086
Alex Klein2008aee2019-08-20 16:25:27 -060087 # Positional service_method argument validation.
George Engelbrechtd3de8df2019-09-04 18:15:05 -060088 parts = opts.service_method.split('/')
89 if len(parts) != 2:
Alex Kleind815ca62020-01-10 12:21:30 -070090 parser.error('Invalid service/method specification format. It should be '
91 'something like chromite.api.SdkService/Create.')
George Engelbrechtd3de8df2019-09-04 18:15:05 -060092
Alex Klein00b1f1e2019-02-08 13:53:42 -070093 if opts.service_method not in methods:
Alex Klein00aa8072019-04-15 16:36:00 -060094 # Unknown method, try to match against known methods and make a suggestion.
95 # This is just for developer sanity, e.g. misspellings when testing.
Alex Klein2008aee2019-08-20 16:25:27 -060096 matched = matching.GetMostLikelyMatchedObject(
97 methods, opts.service_method, matched_score_threshold=0.6)
Alex Klein00b1f1e2019-02-08 13:53:42 -070098 error = 'Unrecognized service name.'
99 if matched:
100 error += '\nDid you mean: \n%s' % '\n'.join(matched)
101 parser.error(error)
102
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700103 opts.service = parts[0]
104 opts.method = parts[1]
105
Alex Kleine191ed62020-02-27 15:59:55 -0700106 # Input and output validation.
107 if not opts.output_binary and not opts.output_json:
108 parser.error('At least one output file must be specified.')
Alex Klein2008aee2019-08-20 16:25:27 -0600109
Alex Kleine191ed62020-02-27 15:59:55 -0700110 if not os.path.exists(opts.input_binary or opts.input_json):
Alex Klein5bcb4d22019-03-21 13:51:54 -0600111 parser.error('Input file does not exist.')
112
Alex Kleind815ca62020-01-10 12:21:30 -0700113 config_msg = build_api_config_pb2.BuildApiConfig()
114 if opts.config_json:
Alex Kleine191ed62020-02-27 15:59:55 -0700115 handler = message_util.get_message_handler(opts.config_json,
116 message_util.FORMAT_JSON)
117 else:
118 handler = message_util.get_message_handler(opts.config_binary,
119 message_util.FORMAT_BINARY)
120
121 if opts.config_json or opts.config_binary:
122 # We have been given a config, so read it.
Alex Kleind815ca62020-01-10 12:21:30 -0700123 try:
Alex Kleine191ed62020-02-27 15:59:55 -0700124 handler.read_into(config_msg)
125 except message_util.Error as e:
126 parser.error(str(e))
Alex Kleind815ca62020-01-10 12:21:30 -0700127
128 opts.config = api_config_lib.build_config_from_proto(config_msg)
Alex Kleine191ed62020-02-27 15:59:55 -0700129 opts.config_handler = handler
Alex Klein69339cc2019-07-22 14:08:35 -0600130
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700131 opts.Freeze()
132 return opts
133
134
Alex Kleine191ed62020-02-27 15:59:55 -0700135def _get_io_handlers(opts):
136 """Build the input and output handlers."""
137 if opts.input_binary:
138 input_handler = message_util.get_message_handler(opts.input_binary,
139 message_util.FORMAT_BINARY)
140 else:
141 input_handler = message_util.get_message_handler(opts.input_json,
142 message_util.FORMAT_JSON)
143
144 output_handlers = []
145 if opts.output_binary:
146 handler = message_util.get_message_handler(opts.output_binary,
147 message_util.FORMAT_BINARY)
148 output_handlers.append(handler)
149 if opts.output_json:
150 handler = message_util.get_message_handler(opts.output_json,
151 message_util.FORMAT_JSON)
152 output_handlers.append(handler)
153
154 return input_handler, output_handlers
155
156
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700157def main(argv):
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700158 with cros_build_lib.ContextManagerStack() as stack:
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700159 router = router_lib.GetRouter()
160 opts = _ParseArgs(argv, router)
Alex Klein00b1f1e2019-02-08 13:53:42 -0700161
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700162 if opts.tee_log:
163 stack.Add(tee.Tee, opts.tee_log)
164 logging.info('Teeing stdout and stderr to %s', opts.tee_log)
Alex Kleind815ca62020-01-10 12:21:30 -0700165 if opts.config.log_path:
166 stack.Add(tee.Tee, opts.config.log_path)
167 logging.info('Teeing stdout and stderr to %s', opts.config.log_path)
Michael Mortensena0515d92020-01-02 11:39:34 -0700168 tee_log_env_value = os.environ.get('BUILD_API_TEE_LOG_FILE')
169 if tee_log_env_value:
170 stack.Add(tee.Tee, tee_log_env_value)
171 logging.info('Teeing stdout and stderr to env path %s', tee_log_env_value)
Alex Klein2008aee2019-08-20 16:25:27 -0600172
Alex Kleind815ca62020-01-10 12:21:30 -0700173 if opts.config.mock_invalid:
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700174 # --mock-invalid handling. We print error messages, but no output is ever
175 # set for validation errors, so we can handle it by just giving back the
176 # correct return code here.
177 return controller.RETURN_CODE_INVALID_INPUT
178
Alex Kleine191ed62020-02-27 15:59:55 -0700179 input_handler, output_handlers = _get_io_handlers(opts)
180
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700181 try:
Alex Kleine191ed62020-02-27 15:59:55 -0700182 return router.Route(opts.service, opts.method, opts.config, input_handler,
183 output_handlers, opts.config_handler)
Michael Mortensen3e86c1e2019-11-21 15:51:54 -0700184 except router_lib.Error as e:
185 # Handle router_lib.Error derivatives nicely, but let anything else bubble
186 # up.
187 cros_build_lib.Die(e)