Skip to content

Commit

Permalink
Initial @rules for Options computation via the engine.
Browse files Browse the repository at this point in the history
  • Loading branch information
kwlzn committed May 31, 2018
1 parent 62d8b26 commit 6d0516e
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/python/pants/bin/local_pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _run(self):
# Bootstrap options and logging.
options_bootstrapper = self._options_bootstrapper or OptionsBootstrapper(env=self._env,
args=self._args)
options, build_config = OptionsInitializer(options_bootstrapper, exiter=self._exiter).setup()
options, build_config = OptionsInitializer(options_bootstrapper).setup()
global_options = options.for_global_scope()

# Apply exiter options.
Expand Down
9 changes: 9 additions & 0 deletions src/python/pants/engine/legacy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ python_library(
],
)

python_library(
name='options_parsing',
sources=['options_parsing.py'],
dependencies=[
'src/python/pants/engine:objects',
'src/python/pants/option',
],
)

python_library(
name='structs',
sources=['structs.py'],
Expand Down
50 changes: 50 additions & 0 deletions src/python/pants/engine/legacy/options_parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

import collections

from pants.engine.rules import RootRule, rule
from pants.engine.selectors import Select
from pants.init.options_initializer import OptionsInitializer
from pants.option.options_bootstrapper import OptionsBootstrapper
from pants.util.objects import datatype


class OptionsParseRequest(datatype(['args', 'env'])):
"""Represents a request for Options computation."""

@classmethod
def create(cls, args, env):
assert isinstance(args, (list, tuple))
return cls(
tuple(args),
tuple(env.items() if isinstance(env, dict) else env)
)


class Options(datatype(['options', 'build_config'])):
"""Represents the result of an Options computation."""


@rule(Options, [Select(OptionsParseRequest)])
def parse_options(parse_request):
options_bootstrapper = OptionsBootstrapper(
env=dict(parse_request.env),
args=parse_request.args
)
options_initializer = OptionsInitializer(options_bootstrapper)
options, build_config = options_initializer.setup(init_logging=False, init_subsystems=False)
options.freeze()
return Options(options, build_config)


def create_options_parsing_rules():
return [
parse_options,
RootRule(Options),
RootRule(OptionsParseRequest),
]
1 change: 1 addition & 0 deletions src/python/pants/init/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ python_library(
'src/python/pants/core_tasks',
'src/python/pants/engine/legacy:address_mapper',
'src/python/pants/engine/legacy:graph',
'src/python/pants/engine/legacy:options_parsing',
'src/python/pants/engine/legacy:parser',
'src/python/pants/engine/legacy:source_mapper',
'src/python/pants/engine/legacy:structs',
Expand Down
2 changes: 2 additions & 0 deletions src/python/pants/init/engine_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pants.engine.legacy.address_mapper import LegacyAddressMapper
from pants.engine.legacy.graph import (LegacyBuildGraph, TransitiveHydratedTargets,
create_legacy_graph_tasks)
from pants.engine.legacy.options_parsing import create_options_parsing_rules
from pants.engine.legacy.parser import LegacyPythonCallbacksParser
from pants.engine.legacy.structs import (AppAdaptor, GoTargetAdaptor, JavaLibraryAdaptor,
JunitTestsAdaptor, PythonLibraryAdaptor,
Expand Down Expand Up @@ -194,6 +195,7 @@ def setup_legacy_graph(pants_ignore_patterns,
create_fs_rules() +
create_process_rules() +
create_graph_rules(address_mapper, symbol_table) +
create_options_parsing_rules() +
rules
)

Expand Down
14 changes: 8 additions & 6 deletions src/python/pants/init/options_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,14 @@ class OptionsInitializer(object):
# Class-level cache for the `BuildConfiguration` object.
_build_configuration = None

def __init__(self, options_bootstrapper, working_set=None, exiter=sys.exit):
def __init__(self, options_bootstrapper, working_set=None):
"""
:param OptionsBootStrapper options_bootstrapper: An options bootstrapper instance.
:param pkg_resources.WorkingSet working_set: The working set of the current run as returned by
PluginResolver.resolve().
:param func exiter: A function that accepts an exit code value and exits (for tests).
"""
self._options_bootstrapper = options_bootstrapper
self._working_set = working_set or PluginResolver(self._options_bootstrapper).resolve()
self._exiter = exiter

@classmethod
def _has_build_configuration(cls):
Expand Down Expand Up @@ -111,15 +109,17 @@ def _install_options(self, options_bootstrapper, build_configuration):
for optionable_cls in distinct_optionable_classes:
optionable_cls.register_options_on_scope(options)

return options

def _install_subsystem_options(self, options):
# Make the options values available to all subsystems.
Subsystem.set_options(options)

return options

def setup(self, init_logging=True):
def setup(self, init_logging=True, init_subsystems=True):
"""Initializes logging, loads backends/plugins and parses options.
:param bool init_logging: Whether or not to initialize logging as part of setup.
:param bool init_subsystems: Whether or not to initialize Subsystem options as part of setup.
:returns: A tuple of (options, build_configuration).
"""
global_bootstrap_options = self._options_bootstrapper.get_bootstrap_options().for_global_scope()
Expand Down Expand Up @@ -148,5 +148,7 @@ def setup(self, init_logging=True):

# Parse and register options.
options = self._install_options(self._options_bootstrapper, build_configuration)
if init_subsystems:
self._install_subsystem_options(options)

return options, build_configuration
22 changes: 22 additions & 0 deletions src/python/pants/option/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class Options(object):
class OptionTrackerRequiredError(Exception):
"""Options requires an OptionTracker instance."""

class FrozenOptionsError(Exception):
"""Options are frozen and can't be mutated."""

@classmethod
def complete_scopes(cls, scope_infos):
"""Expand a set of scopes to include all enclosing scopes.
Expand Down Expand Up @@ -150,6 +153,12 @@ def __init__(self, goals, scope_to_flags, target_specs, passthru, passthru_owner
self._bootstrap_option_values = bootstrap_option_values
self._known_scope_to_info = known_scope_to_info
self._option_tracker = option_tracker
self._frozen = False

@property
def frozen(self):
"""Whether or not this Options object is frozen from writes."""
return self._frozen

@property
def tracker(self):
Expand Down Expand Up @@ -186,6 +195,10 @@ def known_scope_to_info(self):
def scope_to_flags(self):
return self._scope_to_flags

def freeze(self):
"""Freezes this Options instance."""
self._frozen = True

def drop_flag_values(self):
"""Returns a copy of these options that ignores values specified via flags.
Expand Down Expand Up @@ -236,15 +249,21 @@ def passthru_args_for_scope(self, scope):
else:
return []

def _assert_not_frozen(self):
if self._frozen:
raise self.FrozenOptionsError('cannot mutate frozen Options instance {!r}.'.format(self))

def register(self, scope, *args, **kwargs):
"""Register an option in the given scope."""
self._assert_not_frozen()
self.get_parser(scope).register(*args, **kwargs)
deprecated_scope = self.known_scope_to_info[scope].deprecated_scope
if deprecated_scope:
self.get_parser(deprecated_scope).register(*args, **kwargs)

def registration_function_for_optionable(self, optionable_class):
"""Returns a function for registering options on the given scope."""
self._assert_not_frozen()
# 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):
Expand All @@ -258,11 +277,14 @@ def register(*args, **kwargs):

def get_parser(self, scope):
"""Returns the parser for the given scope, so code can register on it directly."""
self._assert_not_frozen()
return self._parser_hierarchy.get_parser_by_scope(scope)

def walk_parsers(self, callback):
self._assert_not_frozen()
self._parser_hierarchy.walk(callback)

# TODO: Eagerly precompute backing data for this?
def for_scope(self, scope, inherit_from_enclosing_scope=True):
"""Return the option values for the given scope.
Expand Down
13 changes: 13 additions & 0 deletions tests/python/pants_test/engine/legacy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,19 @@ python_tests(
tags = {'integration'},
)

python_tests(
name = 'options_parsing',
sources = ['test_options_parsing.py'],
dependencies = [
'3rdparty/python:mock',
'src/python/pants/bin',
'src/python/pants/build_graph',
'src/python/pants/init',
# 'tests/python/pants_test:int-test'
'tests/python/pants_test/engine:util',
]
)

python_tests(
name = 'pants_engine_integration',
sources = ['test_pants_engine_integration.py'],
Expand Down
51 changes: 51 additions & 0 deletions tests/python/pants_test/engine/legacy/test_options_parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

import os
import unittest
from contextlib import contextmanager

from pants.base.build_environment import get_buildroot
from pants.engine.legacy.options_parsing import Options, OptionsParseRequest
from pants.init.engine_initializer import EngineInitializer
from pants_test.engine.util import init_native


class TestEngineOptionsParsing(unittest.TestCase):

_native = init_native()

@contextmanager
def scheduler(self, build_root=None, native=_native):
build_root = build_root or get_buildroot()
work_dir = os.path.join(build_root, '.pants.d')
scheduler = EngineInitializer.setup_legacy_graph(
[],
work_dir,
build_file_imports_behavior='allow',
build_root=build_root,
native=native
)
yield scheduler.scheduler.new_session()

def test_options_parsing_request(self):
with self.scheduler() as scheduler:
products = scheduler.product_request(
Options,
[
OptionsParseRequest.create(
['./pants', '-ldebug', 'binary', 'src/python::'],
dict(PANTS_ENABLE_PANTSD='True', PANTS_BINARIES_BASEURLS='["https://bins.com"]')
)
]
)
options = products[0].options
self.assertIn('binary', options.goals)
global_options = options.for_global_scope()
self.assertEquals(global_options.level, 'debug')
self.assertEquals(global_options.enable_pantsd, True)
self.assertEquals(global_options.binaries_baseurls, ['https://bins.com'])

0 comments on commit 6d0516e

Please sign in to comment.