Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pants-plugins/stevedore_extensions to add teach pants' dependency inference about our runtime-loaded plugins #5869

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
02c5342
pants: add StevedoreExtension target
cognifloyd May 19, 2021
5403639
pants: generate entry_points.txt from StevedoreExtension targets
cognifloyd May 19, 2021
f3105a0
pants: add StevedoreNamespaceField.help
cognifloyd May 19, 2021
33e2eb0
pants: Enable stevedore_extensions plugin and fix errors
cognifloyd May 19, 2021
1b2ff53
pants: shortform Get has to have only 1 comma
cognifloyd May 19, 2021
a20ca96
pants: add stevedore_namespaces dependency inferrence
cognifloyd May 19, 2021
0e33bd3
pants: don't Get within a for loop
cognifloyd May 19, 2021
9efacef
pants: fix stevedore_entry_point_dependencies injection
cognifloyd May 19, 2021
7fd9363
pants: fix inject_stevedore_dependencies injection
cognifloyd May 19, 2021
c9f922d
pants: rename stevedore_dependency_inference -> pytest_dependency_inj…
cognifloyd May 19, 2021
9387062
pants: cleanup stevedore_extensions plugin
cognifloyd May 19, 2021
214271f
pants: cleanup
cognifloyd May 20, 2021
b7e6637
pants: upgrade to pants 2.6.0.dev0 to use PytestPluginSetup
cognifloyd May 20, 2021
eaa7474
pants: add LogLevel to rules so they show up in UI
cognifloyd May 21, 2021
cab63dc
pants: don't use get in a for loop
cognifloyd May 21, 2021
de2191c
pants: reformat plugins with black
cognifloyd May 31, 2021
e37f5af
Reformat with black
cognifloyd May 31, 2021
e02c593
reformat with black
cognifloyd Jun 14, 2021
4f56066
add license header
cognifloyd Jun 14, 2021
8158253
fix flake8 identified issues
cognifloyd Jun 14, 2021
92e573d
update stevedore_extensions pants plugin for api changes in pants 2.7
cognifloyd Oct 14, 2021
ea686b5
update to pants 2.8 (plugin apis, BUILD updates)
cognifloyd May 26, 2022
1936c6c
update to pants 2.9 apis
cognifloyd May 26, 2022
654a14c
update to pants 2.10 apis, resolves
cognifloyd May 26, 2022
2fd18bb
reformat with black
cognifloyd May 26, 2022
376dd26
rename StevedoreDependencies -> StevedoreDependenciesField
cognifloyd Jun 20, 2022
1ad222c
move pytest_dependency_injection to python_dependency_injection
cognifloyd Jun 20, 2022
8d59111
update to pants 2.13.0a1
cognifloyd Jul 1, 2022
9d2af6a
pants plugins updates
cognifloyd Oct 12, 2022
b4b3a8f
finish updating stevedore_extensions pants plugin for new API in pant…
cognifloyd Oct 16, 2022
239d0e5
disable stevedore_extensions plugin until next PR
cognifloyd Jan 17, 2023
df851af
improve stevedore_extensions field documentation
cognifloyd Jan 18, 2023
d37ef51
add provenance to stevedore_extension EntryPoint.parse call
cognifloyd Jan 18, 2023
be248d4
describe stevedore_extensions in pants-plugins/README.md
cognifloyd Jan 18, 2023
993daae
make linters happy
cognifloyd Jan 18, 2023
016e2c5
move find_all_stevedore_extension_targets rule
cognifloyd Jan 18, 2023
0c385d0
add test for resolve_stevedore_entry_points
cognifloyd Jan 18, 2023
d5700ed
add test for infer_stevedore_entry_points_dependencies
cognifloyd Jan 18, 2023
4f1b936
rename python_dependency_injection to python_target_dependencies
cognifloyd Jan 18, 2023
718dcff
add tests for pants-plugins/stevedore_extensions/python_target_depend…
cognifloyd Jan 18, 2023
409b5b7
update copyright to 2023 in pants-plugins/stevedore_extensions
cognifloyd Jan 19, 2023
45e8d04
simplify resolve_stevedore_entry_points based on PR feedback
cognifloyd Jan 21, 2023
06d7141
partially revert to resolve flake8 identified issue
cognifloyd Jan 21, 2023
0776a13
address doc comment PR feedback
cognifloyd Jan 23, 2023
45cb023
update changelog entry
cognifloyd Jan 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Added
working on StackStorm, improve our security posture, and improve CI reliability thanks in part
to pants' use of PEX lockfiles. This is not a user-facing addition.
#5778 #5789 #5817 #5795 #5830 #5833 #5834 #5841 #5840 #5838 #5842 #5837 #5849 #5850
#5846 #5853 #5848 #5847 #5858 #5857 #5860 #5868 #5871
#5846 #5853 #5848 #5847 #5858 #5857 #5860 #5868 #5871 #5869
Contributed by @cognifloyd

* Added a joint index to solve the problem of slow mongo queries for scheduled executions. #5805
Expand Down
7 changes: 7 additions & 0 deletions contrib/runners/noop_runner/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# stevedore_extension(
# name="runner",
# namespace="st2common.runners.runner",
# entry_points={
# "noop": "noop_runner.noop_runner",
# },
# )
12 changes: 12 additions & 0 deletions pants-plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The plugins here add custom goals or other logic into pants.

To see available goals, do "./pants help goals" and "./pants help $goal".

These plugins might be useful outside of the StackStorm project:
- `stevedore_extensions`

These StackStorm-specific plugins might be useful in other StackStorm-related repos.
- `pack_metadata`

Expand Down Expand Up @@ -66,3 +69,12 @@ the `fmt` goal (eg `./pants fmt contrib/schemas::`), the schemas will
be regenerated if any of the files used to generate them have changed.
Also, running the `lint` goal will fail if the schemas need to be
regenerated.

### `stevedore_extensions` plugin

This plugin teaches pants how to infer dependencies on stevedore
extensions (python plugins loaded at runtime via entry points).
This includes the `stevedore_extensions` target and the
`stevedore_namespaces` field. When necessary, it generates an
`entry_points.txt` file so that stevedore can work correctly
within pants sandboxes.
5 changes: 5 additions & 0 deletions pants-plugins/stevedore_extensions/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
python_sources()

python_tests(
name="tests",
)
Empty file.
123 changes: 123 additions & 0 deletions pants-plugins/stevedore_extensions/python_target_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Copyright 2023 The StackStorm Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import defaultdict
from dataclasses import dataclass
from typing import List, Mapping, Tuple

from pants.backend.python.target_types import (
PythonTestTarget,
PythonTestsGeneratorTarget,
PythonTestsDependenciesField,
)
from pants.engine.addresses import Address
from pants.engine.rules import collect_rules, rule, UnionRule
from pants.engine.target import (
AllTargets,
FieldSet,
InferDependenciesRequest,
InferredDependencies,
)
from pants.util.frozendict import FrozenDict
from pants.util.logging import LogLevel
from pants.util.ordered_set import OrderedSet

from stevedore_extensions.target_types import (
AllStevedoreExtensionTargets,
StevedoreEntryPointsField,
StevedoreExtension,
StevedoreNamespaceField,
StevedoreNamespacesField,
)


# -----------------------------------------------------------------------------------------------
# Utility rules to analyze all `StevedoreExtension` targets
# -----------------------------------------------------------------------------------------------


@rule(desc="Find all StevedoreExtension targets in project", level=LogLevel.DEBUG)
def find_all_stevedore_extension_targets(
targets: AllTargets,
) -> AllStevedoreExtensionTargets:
return AllStevedoreExtensionTargets(
tgt for tgt in targets if tgt.has_field(StevedoreEntryPointsField)
)


@dataclass(frozen=True)
class StevedoreExtensions:
"""A mapping of stevedore namespaces to a list of StevedoreExtension targets that provide them"""

mapping: FrozenDict[str, Tuple[StevedoreExtension]]


@rule(
desc="Creating map of stevedore_extension namespaces to StevedoreExtension targets",
level=LogLevel.DEBUG,
)
async def map_stevedore_extensions(
stevedore_extensions: AllStevedoreExtensionTargets,
) -> StevedoreExtensions:
mapping: Mapping[str, List[StevedoreExtension]] = defaultdict(list)
for extension in stevedore_extensions:
mapping[extension[StevedoreNamespaceField].value].append(extension)
return StevedoreExtensions(
FrozenDict((k, tuple(v)) for k, v in sorted(mapping.items()))
)


# -----------------------------------------------------------------------------------------------
# Dependencies for `python_test` and `python_tests` targets
# -----------------------------------------------------------------------------------------------


@dataclass(frozen=True)
class PythonTestsStevedoreNamespaceInferenceFieldSet(FieldSet):
required_fields = (PythonTestsDependenciesField, StevedoreNamespacesField)

stevedore_namespaces: StevedoreNamespacesField


class InferStevedoreNamespaceDependencies(InferDependenciesRequest):
infer_from = PythonTestsStevedoreNamespaceInferenceFieldSet


@rule(
desc="Infer stevedore_extension target dependencies based on namespace list.",
level=LogLevel.DEBUG,
)
async def infer_stevedore_namespace_dependencies(
request: InferStevedoreNamespaceDependencies,
stevedore_extensions: StevedoreExtensions,
) -> InferredDependencies:
namespaces: StevedoreNamespacesField = request.field_set.stevedore_namespaces
if namespaces.value is None:
return InferredDependencies(())

addresses = []
for namespace in namespaces.value:
extensions = stevedore_extensions.mapping.get(namespace, ())
addresses.extend(extension.address for extension in extensions)

result: OrderedSet[Address] = OrderedSet(addresses)
return InferredDependencies(sorted(result))


def rules():
return [
*collect_rules(),
PythonTestsGeneratorTarget.register_plugin_field(StevedoreNamespacesField),
PythonTestTarget.register_plugin_field(StevedoreNamespacesField),
UnionRule(InferDependenciesRequest, InferStevedoreNamespaceDependencies),
]
182 changes: 182 additions & 0 deletions pants-plugins/stevedore_extensions/python_target_dependencies_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Copyright 2023 The StackStorm Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

from textwrap import dedent

import pytest

from pants.backend.python.target_types import (
PythonSourceTarget,
PythonSourcesGeneratorTarget,
PythonTestTarget,
PythonTestsGeneratorTarget,
)
from pants.backend.python.target_types_rules import rules as python_target_types_rules
from pants.engine.addresses import Address
from pants.engine.target import InferredDependencies
from pants.testutil.rule_runner import QueryRule, RuleRunner
from pants.util.frozendict import FrozenDict

from .python_target_dependencies import (
InferStevedoreNamespaceDependencies,
PythonTestsStevedoreNamespaceInferenceFieldSet,
StevedoreExtensions,
rules as stevedore_dep_rules,
)
from .target_types import (
AllStevedoreExtensionTargets,
StevedoreExtension,
)


# random set of runner names to use in tests
st2_runners = ["noop", "python", "foobar"]


@pytest.fixture
def rule_runner() -> RuleRunner:
rule_runner = RuleRunner(
rules=[
*python_target_types_rules(),
*stevedore_dep_rules(),
QueryRule(AllStevedoreExtensionTargets, ()),
QueryRule(StevedoreExtensions, ()),
QueryRule(InferredDependencies, (InferStevedoreNamespaceDependencies,)),
],
target_types=[
PythonSourceTarget,
PythonSourcesGeneratorTarget,
PythonTestTarget,
PythonTestsGeneratorTarget,
StevedoreExtension,
],
)
for runner in st2_runners:
rule_runner.write_files(
{
f"runners/{runner}_runner/BUILD": dedent(
f"""\
stevedore_extension(
name="runner",
namespace="st2common.runners.runner",
entry_points={{
"{runner}": "{runner}_runner.{runner}_runner",
}},
)
stevedore_extension(
name="thing",
namespace="some.thing.else",
entry_points={{
"{runner}": "{runner}_runner.thing",
}},
)
"""
),
f"runners/{runner}_runner/{runner}_runner/BUILD": "python_sources()",
f"runners/{runner}_runner/{runner}_runner/__init__.py": "",
f"runners/{runner}_runner/{runner}_runner/{runner}_runner.py": "",
f"runners/{runner}_runner/{runner}_runner/thing.py": "",
}
)
args = [
"--source-root-patterns=runners/*_runner",
]
rule_runner.set_options(args, env_inherit={"PATH", "PYENV_ROOT", "HOME"})
return rule_runner


# -----------------------------------------------------------------------------------------------
# Tests for utility rules
# -----------------------------------------------------------------------------------------------


def test_find_all_stevedore_extension_targets(rule_runner: RuleRunner) -> None:
assert rule_runner.request(
AllStevedoreExtensionTargets, []
) == AllStevedoreExtensionTargets(
rule_runner.get_target(
Address(f"runners/{runner}_runner", target_name=target_name)
)
for runner in sorted(st2_runners)
for target_name in ["runner", "thing"]
)


def test_map_stevedore_extensions(rule_runner: RuleRunner) -> None:
assert rule_runner.request(StevedoreExtensions, []) == StevedoreExtensions(
FrozenDict(
{
"some.thing.else": tuple(
rule_runner.get_target(
Address(f"runners/{runner}_runner", target_name="thing")
)
for runner in sorted(st2_runners)
),
"st2common.runners.runner": tuple(
rule_runner.get_target(
Address(f"runners/{runner}_runner", target_name="runner")
)
for runner in sorted(st2_runners)
),
}
)
)


# -----------------------------------------------------------------------------------------------
# Tests for dependency inference of python targets (python_tests, etc)
# -----------------------------------------------------------------------------------------------


def test_infer_stevedore_namespace_dependencies(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"src/foobar/BUILD": dedent(
"""\
python_tests(
name="tests",
stevedore_namespaces=["st2common.runners.runner"],
)
"""
),
"src/foobar/test_something.py": "",
}
)

def run_dep_inference(address: Address) -> InferredDependencies:
target = rule_runner.get_target(address)
return rule_runner.request(
InferredDependencies,
[
InferStevedoreNamespaceDependencies(
PythonTestsStevedoreNamespaceInferenceFieldSet.create(target)
)
],
)

# this asserts that only the st2common.runners.runner namespace gets selected.
assert run_dep_inference(
Address(
"src/foobar", target_name="tests", relative_file_path="test_something.py"
),
) == InferredDependencies(
[
Address(
f"runners/{runner}_runner",
target_name="runner",
)
for runner in st2_runners
],
)
39 changes: 39 additions & 0 deletions pants-plugins/stevedore_extensions/register.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2023 The StackStorm Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from pants.backend.codegen import export_codegen_goal

from stevedore_extensions import (
target_types_rules,
rules as stevedore_rules,
python_target_dependencies,
)
from stevedore_extensions.target_types import StevedoreExtension


# TODO: add the entry_points automatically to setup_py
# TODO: add stevedore_namespaces field to python_sources?


def rules():
return [
*target_types_rules.rules(),
*stevedore_rules.rules(),
*python_target_dependencies.rules(),
*export_codegen_goal.rules(),
]


def target_types():
return [StevedoreExtension]
Loading