def __init__(self, env, config, known_scope_infos, args=sys.argv, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scope_infos: ScopeInfos for all scopes that may be encountered. :param args: a list of cmd-line args. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ # We need parsers for all the intermediate scopes, so inherited option values # can propagate through them. complete_known_scope_infos = self.complete_scopes(known_scope_infos) splitter = ArgSplitter(complete_known_scope_infos) self._goals, self._scope_to_flags, self._target_specs, self._passthru, self._passthru_owner = \ splitter.split_args(args) if bootstrap_option_values: target_spec_files = bootstrap_option_values.target_spec_files if target_spec_files: for spec in target_spec_files: with open(spec) as f: self._target_specs.extend(filter(None, [line.strip() for line in f])) self._help_request = splitter.help_request self._parser_hierarchy = ParserHierarchy(env, config, complete_known_scope_infos) self._values_by_scope = {} # Arg values, parsed per-scope on demand. self._bootstrap_option_values = bootstrap_option_values self._known_scopes = set([s[0] for s in known_scope_infos])
def create(cls, env, config, known_scope_infos, args=None, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param :class:`pants.option.config.Config` config: data from a config file. :param known_scope_infos: ScopeInfos for all scopes that may be encountered. :param args: a list of cmd-line args; defaults to `sys.argv` if None is supplied. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ # We need parsers for all the intermediate scopes, so inherited option values # can propagate through them. complete_known_scope_infos = cls.complete_scopes(known_scope_infos) splitter = ArgSplitter(complete_known_scope_infos) args = sys.argv if args is None else args goals, scope_to_flags, target_specs, passthru, passthru_owner, unknown_scopes = splitter.split_args(args) option_tracker = OptionTracker() if bootstrap_option_values: target_spec_files = bootstrap_option_values.target_spec_files if target_spec_files: for spec in target_spec_files: with open(spec, 'r') as f: target_specs.extend([line for line in [line.strip() for line in f] if line]) help_request = splitter.help_request parser_hierarchy = ParserHierarchy(env, config, complete_known_scope_infos, option_tracker) bootstrap_option_values = bootstrap_option_values known_scope_to_info = {s.scope: s for s in complete_known_scope_infos} return cls(goals, scope_to_flags, target_specs, passthru, passthru_owner, help_request, parser_hierarchy, bootstrap_option_values, known_scope_to_info, option_tracker, unknown_scopes)
def __init__(self, env, config, known_scopes, args=sys.argv, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ splitter = ArgSplitter(known_scopes) self._goals, self._scope_to_flags, self._target_specs, self._passthru, self._passthru_owner = \ splitter.split_args(args) if bootstrap_option_values: target_spec_files = bootstrap_option_values.target_spec_files if target_spec_files: for spec in target_spec_files: with open(spec) as f: self._target_specs.extend(filter(None, [line.strip() for line in f])) self._help_request = splitter.help_request self._parser_hierarchy = ParserHierarchy(env, config, known_scopes, self._help_request) self._values_by_scope = {} # Arg values, parsed per-scope on demand. self._bootstrap_option_values = bootstrap_option_values self._known_scopes = set(known_scopes)
def create( cls, env: Mapping[str, str], config: Config, known_scope_infos: Iterable[ScopeInfo], args: Optional[Sequence[str]] = None, bootstrap_option_values: Optional[OptionValueContainer] = None, ) -> "Options": """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file. :param known_scope_infos: ScopeInfos for all scopes that may be encountered. :param args: a list of cmd-line args; defaults to `sys.argv` if None is supplied. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ # We need parsers for all the intermediate scopes, so inherited option values # can propagate through them. complete_known_scope_infos = cls.complete_scopes(known_scope_infos) splitter = ArgSplitter(complete_known_scope_infos) args = sys.argv if args is None else args split_args = splitter.split_args(args) if split_args.passthru and len(split_args.goals) > 1: raise cls.AmbiguousPassthroughError( f"Specifying multiple goals (in this case: {split_args.goals}) " "along with passthrough args (args after `--`) is ambiguous.\n" "Try either specifying only a single goal, or passing the passthrough args " "directly to the relevant consumer via its associated flags." ) option_tracker = OptionTracker() if bootstrap_option_values: spec_files = bootstrap_option_values.spec_files if spec_files: for spec_file in spec_files: with open(spec_file, "r") as f: split_args.specs.extend( [line for line in [line.strip() for line in f] if line] ) help_request = splitter.help_request parser_hierarchy = ParserHierarchy(env, config, complete_known_scope_infos, option_tracker) known_scope_to_info = {s.scope: s for s in complete_known_scope_infos} return cls( goals=split_args.goals, scope_to_flags=split_args.scope_to_flags, specs=split_args.specs, passthru=split_args.passthru, help_request=help_request, parser_hierarchy=parser_hierarchy, bootstrap_option_values=bootstrap_option_values, known_scope_to_info=known_scope_to_info, option_tracker=option_tracker, unknown_scopes=split_args.unknown_scopes, )
def __init__(self, env, config, known_scopes, args=sys.argv, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ splitter = ArgSplitter(known_scopes) self._goals, self._scope_to_flags, self._target_specs, self._passthru, self._passthru_owner = \ splitter.split_args(args) self._is_help = splitter.is_help self._parser_hierarchy = ParserHierarchy(env, config, known_scopes) self._values_by_scope = {} # Arg values, parsed per-scope on demand. self._bootstrap_option_values = bootstrap_option_values self._known_scopes = set(known_scopes)
def create( cls, env, config, known_scope_infos, args=None, bootstrap_option_values=None, option_tracker=None, ): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scope_infos: ScopeInfos for all scopes that may be encountered. :param args: a list of cmd-line args; defaults to `sys.argv` if None is supplied. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. :param :class:`pants.option.option_tracker.OptionTracker` option_tracker: option tracker instance to record how option values were assigned. """ # We need parsers for all the intermediate scopes, so inherited option values # can propagate through them. complete_known_scope_infos = cls.complete_scopes(known_scope_infos) splitter = ArgSplitter(complete_known_scope_infos) args = sys.argv if args is None else args goals, scope_to_flags, target_specs, passthru, passthru_owner = splitter.split_args( args) if not option_tracker: raise cls.OptionTrackerRequiredError() if bootstrap_option_values: target_spec_files = bootstrap_option_values.target_spec_files if target_spec_files: for spec in target_spec_files: with open(spec) as f: target_specs.extend( filter(None, [line.strip() for line in f])) help_request = splitter.help_request parser_hierarchy = ParserHierarchy(env, config, complete_known_scope_infos, option_tracker) values_by_scope = {} # Arg values, parsed per-scope on demand. bootstrap_option_values = bootstrap_option_values known_scope_to_info = {s.scope: s for s in complete_known_scope_infos} return cls(goals, scope_to_flags, target_specs, passthru, passthru_owner, help_request, parser_hierarchy, values_by_scope, bootstrap_option_values, known_scope_to_info, option_tracker)
def __init__(self, env, config, known_scopes, args=sys.argv, legacy_parser=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get(section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param legacy_parser: optional instance of optparse.OptionParser, used to register and access the old-style flags during migration. """ splitter = ArgSplitter(known_scopes) self._scope_to_flags, self._target_specs = splitter.split_args(args) self._is_help = splitter.is_help self._parser_hierarchy = ParserHierarchy(env, config, known_scopes, legacy_parser) self._legacy_parser = legacy_parser # Old-style options, used temporarily during transition. self._legacy_values = None # Values parsed from old-stype options. self._values_by_scope = {} # Arg values, parsed per-scope on demand.
def __init__(self, env, config, known_scopes, args=sys.argv, legacy_parser=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get(section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param legacy_parser: optional instance of optparse.OptionParser, used to register and access the old-style flags during migration. """ splitter = ArgSplitter(known_scopes) self._scope_to_flags, self._target_specs, self._passthru = splitter.split_args(args) self._is_help = splitter.is_help self._parser_hierarchy = ParserHierarchy(env, config, known_scopes, legacy_parser) self._legacy_parser = legacy_parser # Old-style options, used temporarily during transition. self._legacy_values = None # Values parsed from old-stype options. self._values_by_scope = {} # Arg values, parsed per-scope on demand.
class Options(object): """The outward-facing API for interacting with options. Supports option registration and fetching option values. Examples: The value in global scope of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in global scope. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the PANTS_FOO_BAR environment variable. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the --foo-bar flag in global scope. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the PANTS_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in scope 'compile') will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini (because of automatic config file fallback to that section). - The hard-coded value provided at registration time. - None. """ GLOBAL_SCOPE = GLOBAL_SCOPE # Custom option types. You can specify these with type= when registering options. # A dict-typed option. dict = staticmethod(custom_types.dict_type) # A list-typed option. Note that this is different than an action='append' option: # An append option will append the cmd-line values to the default. A list-typed option # will replace the default with the cmd-line value. list = staticmethod(custom_types.list_type) def __init__(self, env, config, known_scopes, args=sys.argv, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ splitter = ArgSplitter(known_scopes) self._goals, self._scope_to_flags, self._target_specs, self._passthru, self._passthru_owner = \ splitter.split_args(args) if bootstrap_option_values: target_spec_files = bootstrap_option_values.target_spec_files if target_spec_files: for spec in target_spec_files: with open(spec) as f: self._target_specs.extend(filter(None, [line.strip() for line in f])) self._help_request = splitter.help_request self._parser_hierarchy = ParserHierarchy(env, config, known_scopes, self._help_request) self._values_by_scope = {} # Arg values, parsed per-scope on demand. self._bootstrap_option_values = bootstrap_option_values self._known_scopes = set(known_scopes) @property def target_specs(self): """The targets to operate on.""" return self._target_specs @property def goals(self): """The requested goals, in the order specified on the cmd line.""" return self._goals def is_known_scope(self, scope): """Whether the given scope is known by this instance.""" return scope in self._known_scopes def passthru_args_for_scope(self, scope): # Passthru args "belong" to the last scope mentioned on the command-line. # Note: If that last scope is a goal, we allow all tasks in that goal to access the passthru # args. This is to allow the more intuitive # pants run <target> -- <passthru args> # instead of requiring # pants run.py <target> -- <passthru args>. # # However note that in the case where multiple tasks run in the same goal, e.g., # pants test <target> -- <passthru args> # Then, e.g., both junit and pytest will get the passthru args even though the user probably # only intended them to go to one of them. If the wrong one is not a no-op then the error will # be unpredictable. However this is not a common case, and can be circumvented with an # explicit test.pytest or test.junit scope. if (scope and self._passthru_owner and scope.startswith(self._passthru_owner) and (len(scope) == len(self._passthru_owner) or scope[len(self._passthru_owner)] == '.')): return self._passthru else: return [] def register(self, scope, *args, **kwargs): """Register an option in the given scope, using argparse params.""" self.get_parser(scope).register(*args, **kwargs) def register_global(self, *args, **kwargs): """Register an option in the global scope, using argparse params.""" self.register(GLOBAL_SCOPE, *args, **kwargs) def registration_function_for_global_scope(self): """Returns a function for registering argparse args on the global scope.""" return self.registration_function_for_scope(GLOBAL_SCOPE) def registration_function_for_scope(self, scope): """Returns a function for registering argparse args on the given scope.""" # TODO(benjy): Make this an instance of a class that implements __call__, so we can # docstring it, and so it's less weird than attatching properties to a function. def register(*args, **kwargs): self.register(scope, *args, **kwargs) # Clients can access the bootstrap option values as register.bootstrap. register.bootstrap = self.bootstrap_option_values() # Clients can access the scope as register.scope. register.scope = scope return register def get_parser(self, scope): """Returns the parser for the given scope, so code can register on it directly.""" return self._parser_hierarchy.get_parser_by_scope(scope) def get_global_parser(self): """Returns the parser for the global scope, so code can register on it directly.""" return self.get_parser(GLOBAL_SCOPE) def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() else: values = copy.deepcopy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args(flags_in_scope, values) self._values_by_scope[scope] = values return values def __getitem__(self, scope): # TODO(John Sirois): Mainly supports use of dict<str, dict<str, str>> for mock options in tests, # Consider killing if tests consolidate on using TestOptions instead of the raw dicts. return self.for_scope(scope) def bootstrap_option_values(self): """Return the option values for bootstrap options. General code can also access these values in the global scope. But option registration code cannot, hence this special-casing of this small set of options. """ return self._bootstrap_option_values def for_global_scope(self): """Return the option values for the global scope.""" return self.for_scope(GLOBAL_SCOPE) def print_help_if_requested(self): """If help was requested, print it and return True. Otherwise return False. """ if self._help_request: if self._help_request.version: print(pants_version()) else: self._print_help() return True else: return False def _print_help(self): """Print a help screen, followed by an optional message. Note: Ony useful if called after options have been registered. """ def _maybe_help(scope): s = self._format_help_for_scope(scope) if s != '': # Avoid printing scope name for scope with empty options. print(scope) for line in s.split('\n'): if line != '': # Avoid superfluous blank lines for empty strings. print(' {0}'.format(line)) show_all_help = self._help_request and self._help_request.all_scopes goals = (Goal.all() if show_all_help else [Goal.by_name(goal_name) for goal_name in self.goals]) if goals: for goal in goals: if not goal.ordered_task_names(): print('\nUnknown goal: {}'.format(goal.name)) else: print('\n{0}: {1}\n'.format(goal.name, goal.description)) for scope in goal.known_scopes(): _maybe_help(scope) else: print(pants_release()) print('\nUsage:') print(' ./pants [option ...] [goal ...] [target...] Attempt the specified goals.') print(' ./pants help Get help.') print(' ./pants help [goal] Get help for a goal.') print(' ./pants help-advanced [goal] Get help for a goal\'s advanced options.') print(' ./pants help-all Get help for all goals.') print(' ./pants goals List all installed goals.') print('') print(' [target] accepts two special forms:') print(' dir: to include all targets in the specified directory.') print(' dir:: to include all targets found recursively under the directory.') print('\nFriendly docs:\n http://pantsbuild.github.io/') if show_all_help or not goals: print('\nGlobal options:') print(self.get_global_parser().format_help()) def _format_help_for_scope(self, scope): """Generate a help message for options at the specified scope.""" return self.get_parser(scope).format_help()
class Options(object): """The outward-facing API for interacting with options. Supports option registration and fetching option values. Examples: The value in global scope of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in global scope. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the --foo-bar flag in global scope. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in scope 'compile') will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini (because of automatic config file fallback to that section). - The hard-coded value provided at registration time. - None. """ def __init__(self, env, config, known_scopes, args=sys.argv, legacy_parser=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get(section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param legacy_parser: optional instance of optparse.OptionParser, used to register and access the old-style flags during migration. """ splitter = ArgSplitter(known_scopes) self._scope_to_flags, self._target_specs, self._passthru = splitter.split_args(args) self._is_help = splitter.is_help self._parser_hierarchy = ParserHierarchy(env, config, known_scopes, legacy_parser) self._legacy_parser = legacy_parser # Old-style options, used temporarily during transition. self._legacy_values = None # Values parsed from old-stype options. self._values_by_scope = {} # Arg values, parsed per-scope on demand. @property def target_specs(self): """The targets to operate on.""" return self._target_specs @property def goals(self): """The requested goals, in the order specified on the cmd-line.""" return OrderedSet([g.partition('.')[0] for g in self._scope_to_flags.keys() if g]) @property def is_help(self): """Whether the command line indicates a request for help.""" return self._is_help def set_legacy_values(self, legacy_values): """Override the values with those parsed from legacy flags.""" self._legacy_values = legacy_values def format_global_help(self, legacy=False): """Generate a help message for global options.""" return self.get_global_parser().format_help(legacy=legacy) def format_help(self, scope, legacy=False): """Generate a help message for options at the specified scope.""" return self.get_parser(scope).format_help(legacy=legacy) def register(self, scope, *args, **kwargs): """Register an option in the given scope, using argparse params.""" self.get_parser(scope).register(*args, **kwargs) def register_global(self, *args, **kwargs): """Register an option in the global scope, using argparse params.""" self.register(GLOBAL_SCOPE, *args, **kwargs) def get_parser(self, scope): """Returns the parser for the given scope, so code can register on it directly.""" return self._parser_hierarchy.get_parser_by_scope(scope) def get_global_parser(self): """Returns the parser for the global scope, so code can register on it directly.""" return self.get_parser(GLOBAL_SCOPE) def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() if self._legacy_values: values.update(vars(self._legacy_values)) # Proxy any legacy option values. else: values = copy.deepcopy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args(flags_in_scope, values) self._values_by_scope[scope] = values return values def for_global_scope(self): """Return the option values for the global scope.""" return self.for_scope(GLOBAL_SCOPE) def print_help(self, msg=None, goals=None, legacy=False): """Print a help screen, followed by an optional message. Note: Ony useful if called after options have been registered. """ def _maybe_help(scope): s = self.format_help(scope, legacy=legacy) if s != '': # Avoid superfluous blank lines for empty strings. print(s) goals = goals or self.goals if goals: for goal_name in goals: goal = Goal.by_name(goal_name) if not goal.ordered_task_names(): print('\nUnknown goal: %s' % goal_name) else: print('\n{0}: {1}\n'.format(goal.name, goal.description)) for scope in goal.known_scopes(): _maybe_help(scope) else: print(pants_release()) print('\nUsage:') print(' ./pants [option ...] [goal ...] [target...] Attempt the specified goals.') print(' ./pants help Get help.') print(' ./pants help [goal] Get help for the specified goal.') print(' ./pants goal goals List all installed goals.') print('') print(' [target] accepts two special forms:') print(' dir: to include all targets in the specified directory.') print(' dir:: to include all targets found recursively under the directory.') print('\nFriendly docs:\n http://pantsbuild.github.io/') print('\nGlobal options:') print(self.format_global_help()) if msg is not None: print(msg)
class Options(object): """The outward-facing API for interacting with options. Supports option registration and fetching option values. Examples: The value in global scope of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in global scope. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the --foo-bar flag in global scope. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in scope 'compile') will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini (because of automatic config file fallback to that section). - The hard-coded value provided at registration time. - None. """ # Custom option types. You can specify these with type= when registering options. # A dict-typed option. dict = staticmethod(custom_types.dict_type) # A list-typed option. Note that this is different than an action='append' option: # An append option will append the cmd-line values to the default. A list-typed option # will replace the default with the cmd-line value. list = staticmethod(custom_types.list_type) def __init__(self, env, config, known_scopes, args=sys.argv, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ splitter = ArgSplitter(known_scopes) self._goals, self._scope_to_flags, self._target_specs, self._passthru, self._passthru_owner = \ splitter.split_args(args) self._is_help = splitter.is_help self._parser_hierarchy = ParserHierarchy(env, config, known_scopes) self._values_by_scope = {} # Arg values, parsed per-scope on demand. self._bootstrap_option_values = bootstrap_option_values @property def target_specs(self): """The targets to operate on.""" return self._target_specs @property def goals(self): """The requested goals, in the order specified on the cmd line.""" return self._goals def passthru_args_for_scope(self, scope): # Passthru args "belong" to the last scope mentioned on the command-line. # Note: If that last scope is a goal, we allow all tasks in that goal to access the passthru # args. This is to allow the more intuitive # pants run <target> -- <passthru args> # instead of requiring # pants run.py <target> -- <passthru args>. # # However note that in the case where multiple tasks run in the same goal, e.g., # pants test <target> -- <passthru args> # Then, e.g., both junit and pytest will get the passthru args even though the user probably # only intended them to go to one of them. If the wrong one is not a no-op then the error will # be unpredictable. However this is not a common case, and can be circumvented with an # explicit test.pytest or test.junit scope. if (scope and self._passthru_owner and scope.startswith(self._passthru_owner) and (len(scope) == len(self._passthru_owner) or scope[len(self._passthru_owner)] == '.')): return self._passthru else: return [] @property def is_help(self): """Whether the command line indicates a request for help.""" return self._is_help def format_global_help(self): """Generate a help message for global options.""" return self.get_global_parser().format_help() def format_help(self, scope): """Generate a help message for options at the specified scope.""" return self.get_parser(scope).format_help() def register(self, scope, *args, **kwargs): """Register an option in the given scope, using argparse params.""" self.get_parser(scope).register(*args, **kwargs) def register_global(self, *args, **kwargs): """Register an option in the global scope, using argparse params.""" self.register(GLOBAL_SCOPE, *args, **kwargs) def get_parser(self, scope): """Returns the parser for the given scope, so code can register on it directly.""" return self._parser_hierarchy.get_parser_by_scope(scope) def get_global_parser(self): """Returns the parser for the global scope, so code can register on it directly.""" return self.get_parser(GLOBAL_SCOPE) def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() else: values = copy.deepcopy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args( flags_in_scope, values) self._values_by_scope[scope] = values return values def bootstrap_option_values(self): """Return the option values for bootstrap options. General code can also access these values in the global scope. But option registration code cannot, hence this special-casing of this small set of options. """ return self._bootstrap_option_values def for_global_scope(self): """Return the option values for the global scope.""" return self.for_scope(GLOBAL_SCOPE) def print_help(self, msg=None, goals=None): """Print a help screen, followed by an optional message. Note: Ony useful if called after options have been registered. """ def _maybe_help(scope): s = self.format_help(scope) if s != '': # Avoid printing scope name for scope with empty options. print(scope) for line in s.split('\n'): if line != '': # Avoid superfluous blank lines for empty strings. print(' {0}'.format(line)) goals = goals or self.goals if goals: for goal_name in goals: goal = Goal.by_name(goal_name) if not goal.ordered_task_names(): print('\nUnknown goal: %s' % goal_name) else: print('\n{0}: {1}\n'.format(goal.name, goal.description)) for scope in goal.known_scopes(): _maybe_help(scope) else: print(pants_release()) print('\nUsage:') print( ' ./pants [option ...] [goal ...] [target...] Attempt the specified goals.' ) print(' ./pants help Get help.') print( ' ./pants help [goal] Get help for the specified goal.' ) print( ' ./pants goal goals List all installed goals.' ) print('') print(' [target] accepts two special forms:') print( ' dir: to include all targets in the specified directory.') print( ' dir:: to include all targets found recursively under the directory.' ) print('\nFriendly docs:\n http://pantsbuild.github.io/') print('\nGlobal options:') print(self.format_global_help()) if msg is not None: print(msg)
class Options(object): """The outward-facing API for interacting with options. Supports option registration and fetching option values. Examples: The value in global scope of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in global scope. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the PANTS_FOO_BAR environment variable. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the --foo-bar flag in global scope. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the PANTS_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in scope 'compile') will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini (because of automatic config file fallback to that section). - The hard-coded value provided at registration time. - None. """ GLOBAL_SCOPE = GLOBAL_SCOPE # Custom option types. You can specify these with type= when registering options. # A dict-typed option. dict = staticmethod(custom_types.dict_type) # A list-typed option. Note that this is different than an action='append' option: # An append option will append the cmd-line values to the default. A list-typed option # will replace the default with the cmd-line value. list = staticmethod(custom_types.list_type) # A list-typed option that indicates the list elements are target specs. target_list = staticmethod(custom_types.target_list_type) # A string-typed option that indicates the string is a filepath. file = staticmethod(custom_types.file_type) @classmethod def complete_scopes(cls, scope_infos): """Expand a set of scopes to include all enclosing scopes. E.g., if the set contains `foo.bar.baz`, ensure that it also contains `foo.bar` and `foo`. """ ret = {ScopeInfo.for_global_scope()} for scope_info in scope_infos: ret.add(scope_info) original_scopes = {si.scope for si in scope_infos} for scope_info in scope_infos: scope = scope_info.scope while scope != '': if scope not in original_scopes: ret.add(ScopeInfo(scope, ScopeInfo.INTERMEDIATE)) scope = scope.rpartition('.')[0] return ret def __init__(self, env, config, known_scope_infos, args=sys.argv, bootstrap_option_values=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get[list](section, name, default=)). :param known_scope_infos: ScopeInfos for all scopes that may be encountered. :param args: a list of cmd-line args. :param bootstrap_option_values: An optional namespace containing the values of bootstrap options. We can use these values when registering other options. """ # We need parsers for all the intermediate scopes, so inherited option values # can propagate through them. complete_known_scope_infos = self.complete_scopes(known_scope_infos) splitter = ArgSplitter(complete_known_scope_infos) self._goals, self._scope_to_flags, self._target_specs, self._passthru, self._passthru_owner = \ splitter.split_args(args) if bootstrap_option_values: target_spec_files = bootstrap_option_values.target_spec_files if target_spec_files: for spec in target_spec_files: with open(spec) as f: self._target_specs.extend(filter(None, [line.strip() for line in f])) self._help_request = splitter.help_request self._parser_hierarchy = ParserHierarchy(env, config, complete_known_scope_infos) self._values_by_scope = {} # Arg values, parsed per-scope on demand. self._bootstrap_option_values = bootstrap_option_values self._known_scopes = set([s[0] for s in known_scope_infos]) @property def target_specs(self): """The targets to operate on.""" return self._target_specs @property def goals(self): """The requested goals, in the order specified on the cmd line.""" return self._goals def is_known_scope(self, scope): """Whether the given scope is known by this instance.""" return scope in self._known_scopes def passthru_args_for_scope(self, scope): # Passthru args "belong" to the last scope mentioned on the command-line. # Note: If that last scope is a goal, we allow all tasks in that goal to access the passthru # args. This is to allow the more intuitive # pants run <target> -- <passthru args> # instead of requiring # pants run.py <target> -- <passthru args>. # # However note that in the case where multiple tasks run in the same goal, e.g., # pants test <target> -- <passthru args> # Then, e.g., both junit and pytest will get the passthru args even though the user probably # only intended them to go to one of them. If the wrong one is not a no-op then the error will # be unpredictable. However this is not a common case, and can be circumvented with an # explicit test.pytest or test.junit scope. if (scope and self._passthru_owner and scope.startswith(self._passthru_owner) and (len(scope) == len(self._passthru_owner) or scope[len(self._passthru_owner)] == '.')): return self._passthru else: return [] def register(self, scope, *args, **kwargs): """Register an option in the given scope, using argparse params.""" self.get_parser(scope).register(*args, **kwargs) def registration_function_for_optionable(self, optionable_class): """Returns a function for registering argparse args on the given scope.""" # TODO(benjy): Make this an instance of a class that implements __call__, so we can # docstring it, and so it's less weird than attatching properties to a function. def register(*args, **kwargs): kwargs['registering_class'] = optionable_class self.register(optionable_class.options_scope, *args, **kwargs) # Clients can access the bootstrap option values as register.bootstrap. register.bootstrap = self.bootstrap_option_values() # Clients can access the scope as register.scope. register.scope = optionable_class.options_scope return register def get_parser(self, scope): """Returns the parser for the given scope, so code can register on it directly.""" return self._parser_hierarchy.get_parser_by_scope(scope) def walk_parsers(self, callback): self._parser_hierarchy.walk(callback) def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() else: values = copy.deepcopy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args(flags_in_scope, values) self._values_by_scope[scope] = values return values def registration_args_iter_for_scope(self, scope): """Returns an iterator over the registration arguments of each option in this scope. See `Parser.registration_args_iter` for details. """ return self._parser_hierarchy.get_parser_by_scope(scope).registration_args_iter() def payload_for_scope(self, scope): """Returns a payload representing the options for the given scope.""" payload = Payload() for (name, _, kwargs) in self.registration_args_iter_for_scope(scope): if not kwargs.get('fingerprint', False): continue val = self.for_scope(scope)[name] val_type = kwargs.get('type', '') if val_type == Options.file: field = FileField(val) elif val_type == Options.target_list: field = TargetListField(val) else: field = PrimitiveField(val) payload.add_field(name, field) payload.freeze() return payload def __getitem__(self, scope): # TODO(John Sirois): Mainly supports use of dict<str, dict<str, str>> for mock options in tests, # Consider killing if tests consolidate on using TestOptions instead of the raw dicts. return self.for_scope(scope) def bootstrap_option_values(self): """Return the option values for bootstrap options. General code can also access these values in the global scope. But option registration code cannot, hence this special-casing of this small set of options. """ return self._bootstrap_option_values def for_global_scope(self): """Return the option values for the global scope.""" return self.for_scope(GLOBAL_SCOPE) def print_help_if_requested(self): """If help was requested, print it and return True. Otherwise return False. """ if self._help_request: if self._help_request.version: print(pants_version()) else: self._print_help() return True else: return False def _print_help(self): """Print a help screen, followed by an optional message. Note: Ony useful if called after options have been registered. """ show_all_help = self._help_request and self._help_request.all_scopes goals = (Goal.all() if show_all_help else [Goal.by_name(goal_name) for goal_name in self.goals]) if goals: for goal in goals: if not goal.ordered_task_names(): print('\nUnknown goal: {}'.format(goal.name)) else: print('\n{0}: {1}\n'.format(goal.name, goal.description)) for scope_info in goal.known_scope_infos(): help_str = self._format_help_for_scope(scope_info.scope) if help_str: print(help_str) else: print(pants_release()) print('\nUsage:') print(' ./pants [option ...] [goal ...] [target...] Attempt the specified goals.') print(' ./pants help Get help.') print(' ./pants help [goal] Get help for a goal.') print(' ./pants help-advanced [goal] Get help for a goal\'s advanced options.') print(' ./pants help-all Get help for all goals.') print(' ./pants goals List all installed goals.') print('') print(' [target] accepts two special forms:') print(' dir: to include all targets in the specified directory.') print(' dir:: to include all targets found recursively under the directory.') print('\nFriendly docs:\n http://pantsbuild.github.io/') if show_all_help or not goals: print(self.get_parser(GLOBAL_SCOPE).format_help('Global', self._help_request.advanced)) def _format_help_for_scope(self, scope): """Generate a help message for options at the specified scope.""" return self.get_parser(scope).format_help(scope, self._help_request.advanced)
class Options(object): """The outward-facing API for interacting with options. Supports option registration and fetching option values. Examples: The value in global scope of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in global scope. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in global scope) will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the --foo-bar flag in global scope. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the PANTS_DEFAULT_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini. - The hard-coded value provided at registration time. - None. The value in scope 'compile.java' of option '--foo-bar' (registered in scope 'compile') will be selected in the following order: - The value of the --foo-bar flag in scope 'compile.java'. - The value of the --foo-bar flag in scope 'compile'. - The value of the PANTS_COMPILE_JAVA_FOO_BAR environment variable. - The value of the PANTS_COMPILE_FOO_BAR environment variable. - The value of the foo_bar key in the [compile.java] section of pants.ini. - The value of the foo_bar key in the [compile] section of pants.ini. - The value of the foo_bar key in the [DEFAULT] section of pants.ini (because of automatic config file fallback to that section). - The hard-coded value provided at registration time. - None. """ def __init__(self, env, config, known_scopes, args=sys.argv, legacy_parser=None): """Create an Options instance. :param env: a dict of environment variables. :param config: data from a config file (must support config.get(section, name, default=)). :param known_scopes: a list of all possible scopes that may be encountered. :param args: a list of cmd-line args. :param legacy_parser: optional instance of optparse.OptionParser, used to register and access the old-style flags during migration. """ splitter = ArgSplitter(known_scopes) self._scope_to_flags, self._target_specs = splitter.split_args(args) self._is_help = splitter.is_help self._parser_hierarchy = ParserHierarchy(env, config, known_scopes, legacy_parser) self._legacy_parser = legacy_parser # Old-style options, used temporarily during transition. self._legacy_values = None # Values parsed from old-stype options. self._values_by_scope = {} # Arg values, parsed per-scope on demand. @property def target_specs(self): """The targets to operate on.""" return self._target_specs @property def goals(self): """The requested goals.""" # TODO: Order them in some way? We don't know anything about the topological # order here, but it would be nice to, e.g., display help in that order. return set( [g.partition('.')[0] for g in self._scope_to_flags.keys() if g]) @property def is_help(self): """Whether the command line indicates a request for help.""" return self._is_help def set_legacy_values(self, legacy_values): """Override the values with those parsed from legacy flags.""" self._legacy_values = legacy_values def format_global_help(self, legacy=False): """Generate a help message for global options.""" return self.get_global_parser().format_help(legacy=legacy) def format_help(self, scope, legacy=False): """Generate a help message for options at the specified scope.""" return self.get_parser(scope).format_help(legacy=legacy) def register(self, scope, *args, **kwargs): """Register an option in the given scope, using argparse params.""" self.get_parser(scope).register(*args, **kwargs) def register_global(self, *args, **kwargs): """Register an option in the global scope, using argparse params.""" self.register(GLOBAL_SCOPE, *args, **kwargs) def get_parser(self, scope): """Returns the parser for the given scope, so code can register on it directly.""" return self._parser_hierarchy.get_parser_by_scope(scope) def get_global_parser(self): """Returns the parser for the global scope, so code can register on it directly.""" return self.get_parser(GLOBAL_SCOPE) def for_scope(self, scope): """Return the option values for the given scope. Values are attributes of the returned object, e.g., options.foo. Computed lazily per scope. """ # Short-circuit, if already computed. if scope in self._values_by_scope: return self._values_by_scope[scope] # First get enclosing scope's option values, if any. if scope == GLOBAL_SCOPE: values = OptionValueContainer() if self._legacy_values: values.update(vars( self._legacy_values)) # Proxy any legacy option values. else: values = copy.copy(self.for_scope(scope.rpartition('.')[0])) # Now add our values. try: flags_in_scope = self._scope_to_flags.get(scope, []) self._parser_hierarchy.get_parser_by_scope(scope).parse_args( flags_in_scope, values) self._values_by_scope[scope] = values return values except ParseError as e: self.print_help(str(e)) sys.exit(1) def for_global_scope(self): """Return the option values for the global scope.""" return self.for_scope(GLOBAL_SCOPE) def print_help(self, msg=None, goals=None, legacy=False): """Print a help screen, followed by an optional message. Note: Ony useful if called after options have been registered. """ def _maybe_print(s): if s != '': # Avoid superfluous blank lines for empty strings. print(s) goals = goals or self.goals if goals: for goal_name in goals: goal = Goal.by_name(goal_name) if not goal.ordered_task_names(): print('\nUnknown goal: %s' % goal_name) else: _maybe_print( self.format_help('%s' % goal.name, legacy=legacy)) for task_name in goal.ordered_task_names(): if task_name != goal.name: # Otherwise we registered on the goal scope. scope = '%s.%s' % (goal.name, task_name) _maybe_print(self.format_help(scope, legacy=legacy)) else: print(pants_release()) print('\nUsage:') print( ' ./pants [option ...] [goal ...] [target...] Attempt the specified goals.' ) print(' ./pants help Get help.') print( ' ./pants help [goal] Get help for the specified goal.' ) print( ' ./pants goals List all installed goals.' ) print('') print(' [target] accepts two special forms:') print( ' dir: to include all targets in the specified directory.') print( ' dir:: to include all targets found recursively under the directory.' ) print('\nFriendly docs:\n http://pantsbuild.github.io/') print('\nGlobal options:') print(self.format_global_help()) if msg is not None: print(msg)