| # Copyright 2017 The LUCI Authors. All rights reserved. |
| # Use of this source code is governed under the Apache License, Version 2.0 |
| # that can be found in the LICENSE file. |
| |
| """Common arguments to the various recipes.py subcommands. |
| |
| This is in a separate file for recipes.py for testing purposes. |
| """ |
| |
| import collections |
| import json |
| import logging |
| import os |
| |
| from . import package_io |
| |
| from . import env |
| |
| import argparse # this is vendored |
| |
| from . import arguments_pb2 |
| |
| from google.protobuf import json_format as jsonpb |
| |
| |
| def add_common_args(parser): |
| class ProjectOverrideAction(argparse.Action): |
| def __call__(self, parser, namespace, values, option_string=None): |
| p = values.split('=', 2) |
| if len(p) != 2: |
| raise ValueError('Override must have the form: repo=path') |
| project_id, path = p |
| |
| v = getattr(namespace, self.dest) |
| |
| if v.get(project_id): |
| raise ValueError('An override is already defined for [%s] (%s)' % ( |
| project_id, v[project_id])) |
| path = os.path.abspath(os.path.expanduser(path)) |
| if not os.path.isdir(path): |
| raise ValueError('Override path [%s] is not a directory' % (path,)) |
| v[project_id] = path |
| |
| def package_type(value): |
| if not os.path.isfile(value): |
| raise argparse.ArgumentTypeError( |
| 'Given recipes config file %r does not exist.' % (value,)) |
| return package_io.PackageFile(value) |
| |
| parser.add_argument( |
| '--package', |
| type=package_type, required=True, |
| help='Path to recipes.cfg of the recipe package to operate on' |
| ', usually in infra/config/recipes.cfg') |
| parser.add_argument( |
| '--verbose', '-v', action='count', |
| help='Increase logging verboisty') |
| parser.add_argument('-O', '--project-override', metavar='ID=PATH', |
| action=ProjectOverrideAction, default=collections.OrderedDict(), |
| help='Override a project repository path with a local one.') |
| parser.add_argument('--use-bootstrap', action='store_true', help='Deprecated') |
| parser.add_argument('--disable-bootstrap', action='store_false', |
| dest='use_bootstrap', help='Deprecated') |
| |
| def operational_args_type(value): |
| with open(value) as fd: |
| return jsonpb.ParseDict(json.load(fd), arguments_pb2.Arguments()) |
| |
| parser.set_defaults( |
| operational_args=arguments_pb2.Arguments( |
| engine_flags=arguments_pb2.Arguments.EngineFlags()), |
| postprocess_func=lambda parser, args: None, |
| ) |
| |
| parser.add_argument( |
| '--operational-args-path', |
| dest='operational_args', |
| type=operational_args_type, |
| help='The path to an operational Arguments file. If provided, this file ' |
| 'must contain a JSONPB-encoded Arguments protobuf message, and will ' |
| 'be integrated into the runtime parameters.') |
| |
| def post_process_args(parser, args): |
| # TODO(iannucci): We should always do logging.basicConfig() (probably with |
| # logging.WARNING), even if no verbose is passed. However we need to be |
| # careful as this could cause issues with spurious/unexpected output. |
| # I think it's risky enough to do in a different CL. |
| |
| if args.verbose > 0: |
| logging.basicConfig() |
| logging.getLogger().setLevel(logging.INFO) |
| if args.verbose > 1: |
| logging.getLogger().setLevel(logging.DEBUG) |
| |
| try: |
| spec = args.package.read() |
| except Exception as ex: |
| parser.error('bad --package %r: %s' % (args.package.path, ex.message,)) |
| |
| extra = set(args.project_override).difference(set(spec.deps)) |
| if extra: |
| parser.error( |
| "attempted to override %r, which don't appear in recipes.cfg" % |
| (extra,)) |
| |
| return post_process_args |