Skip to content

Commit f9b2ed7

Browse files
cosmicexplorerwisechengyi
authored andcommitted
Expose rules from language backends with an application to python_dist() creation (#5747)
### Problem It's not possible to use rules defined outside of `src/python/pants/engine` in the rest of Pants. ### Solution Modify [`load_backend()` in `extension_loader.py`](https://github.com/cosmicexplorer/pants/blob/c9dab55621863af87c3999c09f980b594f73ef78/src/python/pants/init/extension_loader.py#L113) to understand a `rules` entrypoint. If a backend has a method named `rules()` defined in its register.py, that method will be invoked to get a list of rules to make the scheduler with. ### Result We can now use rules defined in language backends. [I created a `register.py` in the native backend](https://github.com/cosmicexplorer/pants/blob/c9dab55621863af87c3999c09f980b594f73ef78/src/python/pants/backend/native/register.py) so that we can test that this works ([it's consumed in BuildLocalPythonDistributions](https://github.com/cosmicexplorer/pants/blob/c9dab55621863af87c3999c09f980b594f73ef78/src/python/pants/backend/python/tasks/build_local_python_distributions.py#L116)). I have a diff that expands the native backend which depends on this functionality. Note: Extending this to cover plugins as well is trivial (make the exact same change, but to `load_plugins()` instead).
1 parent b9d3ef7 commit f9b2ed7

20 files changed

+183
-17
lines changed

build-support/bin/release.sh

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ function execute_packaged_pants_with_internal_backends() {
128128
'pants.backend.docgen',\
129129
'pants.backend.graph_info',\
130130
'pants.backend.jvm',\
131+
'pants.backend.native',\
131132
'pants.backend.project_info',\
132133
'pants.backend.python',\
133134
'internal_backend.repositories',\

src/python/pants/backend/native/BUILD

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# coding=utf-8
2+
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
3+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
4+
5+
python_library(
6+
dependencies=[
7+
'src/python/pants/backend/native/subsystems',
8+
'src/python/pants/engine:rules',
9+
'src/python/pants/util:objects',
10+
'src/python/pants/util:osutil',
11+
],
12+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# coding=utf-8
2+
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
3+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
4+
5+
from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
6+
unicode_literals, with_statement)
7+
8+
from pants.backend.native.subsystems.native_toolchain import NativeToolchain
9+
from pants.engine.rules import RootRule, SingletonRule
10+
from pants.util.objects import datatype
11+
from pants.util.osutil import get_normalized_os_name
12+
13+
14+
class Platform(datatype(['normalized_os_name'])):
15+
16+
def __new__(cls):
17+
return super(Platform, cls).__new__(cls, get_normalized_os_name())
18+
19+
20+
def rules():
21+
return [
22+
RootRule(NativeToolchain),
23+
SingletonRule(Platform, Platform()),
24+
]

src/python/pants/backend/python/register.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from pants.backend.python.tasks.python_run import PythonRun
2626
from pants.backend.python.tasks.resolve_requirements import ResolveRequirements
2727
from pants.backend.python.tasks.select_interpreter import SelectInterpreter
28-
from pants.backend.python.tasks.setup_py import SetupPy
28+
from pants.backend.python.tasks.setup_py import SetupPy, create_setup_py_rules
2929
from pants.build_graph.build_file_aliases import BuildFileAliases
3030
from pants.build_graph.resources import Resources
3131
from pants.goal.task_registrar import TaskRegistrar as task
@@ -65,3 +65,7 @@ def register_goals():
6565
task(name='setup-py', action=SetupPy).install()
6666
task(name='py', action=PythonBinaryCreate).install('binary')
6767
task(name='isort', action=IsortPythonTask).install('fmt')
68+
69+
70+
def rules():
71+
return create_setup_py_rules()

src/python/pants/backend/python/tasks/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ python_library(
1818
'src/python/pants/base:fingerprint_strategy',
1919
'src/python/pants/base:hash_utils',
2020
'src/python/pants/base:specs',
21+
'src/python/pants/engine:rules',
22+
'src/python/pants/engine:selectors',
2123
'src/python/pants/build_graph',
2224
'src/python/pants/invalidation',
2325
'src/python/pants/python',

src/python/pants/backend/python/tasks/build_local_python_distributions.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
from pants.backend.python.python_requirement import PythonRequirement
1717
from pants.backend.python.targets.python_distribution import PythonDistribution
1818
from pants.backend.python.targets.python_requirement_library import PythonRequirementLibrary
19-
from pants.backend.python.tasks.setup_py import SetupPyRunner
19+
from pants.backend.python.tasks.setup_py import SetupPyInvocationEnvironment, SetupPyRunner
2020
from pants.base.build_environment import get_buildroot
2121
from pants.base.exceptions import TargetDefinitionException, TaskError
2222
from pants.base.fingerprint_strategy import DefaultFingerprintStrategy
2323
from pants.build_graph.address import Address
2424
from pants.task.task import Task
25-
from pants.util.contextutil import environment_as, get_joined_path
25+
from pants.util.contextutil import environment_as
2626
from pants.util.dirutil import safe_mkdir
2727
from pants.util.memo import memoized_method
2828

@@ -103,6 +103,11 @@ def _copy_sources(self, dist_tgt, dist_target_dir):
103103
src_relative_to_target_base)
104104
shutil.copyfile(abs_src_path, src_rel_to_results_dir)
105105

106+
def _request_single(self, product, subject):
107+
# This is not supposed to be exposed to Tasks yet -- see #4769 to track the
108+
# status of exposing v2 products in v1 tasks.
109+
return self.context._scheduler.product_request(product, [subject])[0]
110+
106111
# FIXME(cosmicexplorer): We should be isolating the path to just our provided
107112
# toolchain, but this causes errors in Travis because distutils looks for
108113
# "x86_64-linux-gnu-gcc" when linking native extensions. We almost definitely
@@ -111,11 +116,9 @@ def _copy_sources(self, dist_tgt, dist_target_dir):
111116
# compiler installed. Right now we just put our tools at the end of the PATH.
112117
@contextmanager
113118
def _setup_py_invocation_environment(self):
114-
native_toolchain = self._native_toolchain_instance()
115-
native_toolchain_path_entries = native_toolchain.path_entries()
116-
appended_native_toolchain_path = get_joined_path(
117-
native_toolchain_path_entries, os.environ.copy())
118-
with environment_as(PATH=appended_native_toolchain_path):
119+
setup_py_env = self._request_single(
120+
SetupPyInvocationEnvironment, self._native_toolchain_instance())
121+
with environment_as(**setup_py_env.as_env_dict()):
119122
yield
120123

121124
def _create_dist(self, dist_tgt, dist_target_dir, interpreter):

src/python/pants/backend/python/tasks/setup_py.py

+26
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from twitter.common.collections import OrderedSet
2020
from twitter.common.dirutil.chroot import Chroot
2121

22+
from pants.backend.native.subsystems.native_toolchain import NativeToolchain
2223
from pants.backend.python.targets.python_binary import PythonBinary
2324
from pants.backend.python.targets.python_requirement_library import PythonRequirementLibrary
2425
from pants.backend.python.targets.python_target import PythonTarget
@@ -29,10 +30,14 @@
2930
from pants.build_graph.address_lookup_error import AddressLookupError
3031
from pants.build_graph.build_graph import sort_targets
3132
from pants.build_graph.resources import Resources
33+
from pants.engine.rules import rule
34+
from pants.engine.selectors import Select
3235
from pants.task.task import Task
36+
from pants.util.contextutil import get_joined_path
3337
from pants.util.dirutil import safe_rmtree, safe_walk
3438
from pants.util.memo import memoized_property
3539
from pants.util.meta import AbstractClass
40+
from pants.util.objects import datatype
3641

3742

3843
SETUP_BOILERPLATE = """
@@ -74,6 +79,21 @@ def _setup_command(self):
7479
return self.__setup_command
7580

7681

82+
class SetupPyInvocationEnvironment(datatype(['joined_path'])):
83+
84+
def as_env_dict(self):
85+
return {
86+
'PATH': self.joined_path,
87+
}
88+
89+
90+
@rule(SetupPyInvocationEnvironment, [Select(NativeToolchain)])
91+
def get_setup_py_env(native_toolchain):
92+
joined_path = get_joined_path(
93+
native_toolchain.path_entries(), os.environ.copy())
94+
return SetupPyInvocationEnvironment(joined_path)
95+
96+
7797
class TargetAncestorIterator(object):
7898
"""Supports iteration of target ancestor lineages."""
7999

@@ -640,3 +660,9 @@ def create(exported_python_target):
640660
setup_runner = SetupPyRunner(setup_dir, self._run, interpreter=interpreter)
641661
setup_runner.run()
642662
python_dists[exported_python_target] = setup_dir
663+
664+
665+
def create_setup_py_rules():
666+
return [
667+
get_setup_py_env,
668+
]

src/python/pants/bin/engine_initializer.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,19 @@ def create_build_graph(self, target_roots, build_root=None):
109109
class EngineInitializer(object):
110110
"""Constructs the components necessary to run the v2 engine with v1 BuildGraph compatibility."""
111111

112+
@staticmethod
113+
def get_default_build_file_aliases():
114+
_, build_config = OptionsInitializer(OptionsBootstrapper()).setup(init_logging=False)
115+
return build_config.registered_aliases()
116+
112117
@staticmethod
113118
def setup_legacy_graph(pants_ignore_patterns,
114119
workdir,
115120
build_file_imports_behavior,
116121
build_root=None,
117122
native=None,
118123
build_file_aliases=None,
124+
rules=None,
119125
build_ignore_patterns=None,
120126
exclude_target_regexps=None,
121127
subproject_roots=None,
@@ -146,8 +152,10 @@ def setup_legacy_graph(pants_ignore_patterns,
146152
scm = get_scm()
147153

148154
if not build_file_aliases:
149-
_, build_config = OptionsInitializer(OptionsBootstrapper()).setup(init_logging=False)
150-
build_file_aliases = build_config.registered_aliases()
155+
build_file_aliases = EngineInitializer.get_default_build_file_aliases()
156+
157+
if not rules:
158+
rules = []
151159

152160
symbol_table = LegacySymbolTable(build_file_aliases)
153161

@@ -173,7 +181,8 @@ def setup_legacy_graph(pants_ignore_patterns,
173181
create_legacy_graph_tasks(symbol_table) +
174182
create_fs_rules() +
175183
create_graph_rules(address_mapper, symbol_table) +
176-
create_process_rules()
184+
create_process_rules() +
185+
rules
177186
)
178187

179188
scheduler = LocalScheduler(workdir, dict(), tasks, project_tree, native, include_trace_on_error=include_trace_on_error)

src/python/pants/bin/goal_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def _init_graph(self,
107107
self._global_options.build_file_imports,
108108
native=native,
109109
build_file_aliases=self._build_config.registered_aliases(),
110+
rules=self._build_config.rules(),
110111
build_ignore_patterns=build_ignore_patterns,
111112
exclude_target_regexps=exclude_target_regexps,
112113
subproject_roots=subproject_build_roots,

src/python/pants/build_graph/build_configuration.py

+20
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def __init__(self):
3333
self._exposed_object_by_alias = {}
3434
self._exposed_context_aware_object_factory_by_alias = {}
3535
self._subsystems = set()
36+
self._rules = []
3637

3738
def registered_aliases(self):
3839
"""Return the registered aliases exposed in BUILD files.
@@ -129,6 +130,25 @@ def subsystems(self):
129130
"""
130131
return self._subsystems
131132

133+
def register_rules(self, rules):
134+
"""Registers the given rules.
135+
136+
:param rules: The rules to register.
137+
:type rules: :class:`collections.Iterable` containing
138+
:class:`pants.engine.rules.Rule` instances.
139+
"""
140+
141+
if not isinstance(rules, Iterable):
142+
raise TypeError('The rules must be an iterable, given {!r}'.format(rules))
143+
self._rules.extend(rules)
144+
145+
def rules(self):
146+
"""Returns the registered rules.
147+
148+
:rtype list
149+
"""
150+
return self._rules
151+
132152
@memoized_method
133153
def _get_addressable_factory(self, target_type, alias):
134154
return TargetAddressable.factory(target_type=target_type, alias=alias)

src/python/pants/init/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ target(
3737
'src/python/pants/backend/jvm:plugin',
3838
'src/python/pants/backend/project_info:plugin',
3939
'src/python/pants/backend/python:plugin',
40+
'src/python/pants/backend/native',
4041
],
4142
)

src/python/pants/init/extension_loader.py

+4
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,8 @@ def invoke_entrypoint(name):
144144
if subsystems:
145145
build_configuration.register_subsystems(subsystems)
146146

147+
rules = invoke_entrypoint('rules')
148+
if rules:
149+
build_configuration.register_rules(rules)
150+
147151
invoke_entrypoint('register_goals')

src/python/pants/option/global_options.py

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def register_bootstrap_options(cls, register):
7171
default=['pants.backend.graph_info',
7272
'pants.backend.python',
7373
'pants.backend.jvm',
74+
'pants.backend.native',
7475
'pants.backend.codegen.antlr.java',
7576
'pants.backend.codegen.antlr.python',
7677
'pants.backend.codegen.jaxb',

src/python/pants/util/osutil.py

+8
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,13 @@ def normalize_os_name(os_name):
5959
return os_name
6060

6161

62+
def get_normalized_os_name():
63+
return normalize_os_name(get_os_name())
64+
65+
66+
def all_normalized_os_names():
67+
return OS_ALIASES.keys()
68+
69+
6270
def known_os_names():
6371
return reduce(set.union, OS_ALIASES.values())

tests/python/pants_test/backend/python/tasks/BUILD

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ python_tests(
2626
'3rdparty/python:coverage',
2727
'3rdparty/python:mock',
2828
'3rdparty/python:pex',
29+
'src/python/pants/backend/native',
30+
'src/python/pants/backend/python:plugin',
2931
'src/python/pants/backend/python/subsystems',
3032
'src/python/pants/backend/python/targets',
3133
'src/python/pants/backend/python/tasks',
@@ -42,6 +44,7 @@ python_tests(
4244
'src/python/pants/util:contextutil',
4345
'src/python/pants/util:dirutil',
4446
'src/python/pants/util:process_handler',
47+
'tests/python/pants_test/engine:scheduler_test_base',
4548
'tests/python/pants_test/subsystem:subsystem_utils',
4649
'tests/python/pants_test/tasks:task_test_base',
4750
],

tests/python/pants_test/backend/python/tasks/test_build_local_python_distributions.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77

88
from textwrap import dedent
99

10+
from pants.backend.native.register import rules as native_backend_rules
11+
from pants.backend.python.register import rules as python_backend_rules
1012
from pants.backend.python.targets.python_distribution import PythonDistribution
1113
from pants.backend.python.tasks.build_local_python_distributions import \
1214
BuildLocalPythonDistributions
1315
from pants_test.backend.python.tasks.python_task_test_base import PythonTaskTestBase
16+
from pants_test.engine.scheduler_test_base import SchedulerTestBase
1417

1518

16-
class TestBuildLocalPythonDistributions(PythonTaskTestBase):
19+
class TestBuildLocalPythonDistributions(PythonTaskTestBase, SchedulerTestBase):
1720
@classmethod
1821
def task_type(cls):
1922
return BuildLocalPythonDistributions
@@ -43,9 +46,18 @@ def setUp(self):
4346
target_type=PythonDistribution,
4447
sources=sources)
4548

49+
def _scheduling_context(self, **kwargs):
50+
rules = (
51+
native_backend_rules() +
52+
python_backend_rules()
53+
)
54+
scheduler = self.mk_scheduler(rules=rules)
55+
return self.context(scheduler=scheduler, **kwargs)
56+
4657
def test_python_create_distributions(self):
47-
context = self.context(target_roots=[self.python_dist_tgt],
48-
for_task_types=[BuildLocalPythonDistributions])
58+
context = self._scheduling_context(
59+
target_roots=[self.python_dist_tgt],
60+
for_task_types=[BuildLocalPythonDistributions])
4961
self.assertEquals([self.python_dist_tgt], context.build_graph.targets())
5062
python_create_distributions_task = self.create_task(context)
5163
python_create_distributions_task.execute()

tests/python/pants_test/base_test.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def set_options_for_scope(self, scope, **kwargs):
276276

277277
def context(self, for_task_types=None, for_subsystems=None, options=None,
278278
target_roots=None, console_outstream=None, workspace=None,
279-
**kwargs):
279+
scheduler=None, **kwargs):
280280
"""
281281
:API: public
282282
@@ -330,7 +330,8 @@ def context(self, for_task_types=None, for_subsystems=None, options=None,
330330
build_file_parser=self.build_file_parser,
331331
address_mapper=self.address_mapper,
332332
console_outstream=console_outstream,
333-
workspace=workspace)
333+
workspace=workspace,
334+
scheduler=scheduler)
334335
return context
335336

336337
def tearDown(self):

tests/python/pants_test/engine/scheduler_test_base.py

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def mk_scheduler(self,
6060
self._native,
6161
include_trace_on_error=include_trace_on_error)
6262

63+
def context_with_scheduler(self, scheduler, *args, **kwargs):
64+
return self.context(*args, scheduler=scheduler, **kwargs)
65+
6366
def execute(self, scheduler, product, *subjects):
6467
"""Runs an ExecutionRequest for the given product and subjects, and returns the result value."""
6568
request = scheduler.execution_request([product], subjects)

tests/python/pants_test/init/BUILD

+5
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ python_tests(
99
'3rdparty/python:pex',
1010
'3rdparty/python:setuptools',
1111
'src/python/pants/base:exceptions',
12+
'src/python/pants/bin',
1213
'src/python/pants/build_graph',
14+
'src/python/pants/engine:rules',
15+
'src/python/pants/engine:selectors',
1316
'src/python/pants/goal',
1417
'src/python/pants/goal:task_registrar',
1518
'src/python/pants/init',
1619
'src/python/pants/option',
20+
'src/python/pants/pantsd:pants_daemon',
1721
'src/python/pants/subsystem',
1822
'src/python/pants/util:contextutil',
1923
'src/python/pants/util:dirutil',
24+
'src/python/pants/util:objects',
2025
'tests/python/pants_test:base_test',
2126
'tests/python/pants_test/subsystem:subsystem_utils'
2227
],

0 commit comments

Comments
 (0)