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 overrides field to python_sources and python_tests target #13270

Merged
merged 4 commits into from
Oct 19, 2021

Conversation

Eric-Arellano
Copy link
Contributor

As explained in the target generator proposal, this allows ergonomically changing the metadata for a single python_source/python_test target without having to split out a new python_sources/python_tests target and then doing set arithmetic on the sources field to make sure that only one target owns the file.

For example, you can now set:

      overrides={
        "foo_test.py": {"timeout": 120]},
        "bar_test.py": {"timeout": 200]},
        ("foo_test.py", "bar_test.py"): {"tags": ["slow_tests"]},
      }

# Rust tests and lints will be skipped. Delete if not intended.
[ci skip-rust]

# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
# Rust tests and lints will be skipped. Delete if not intended.
[ci skip-rust]

# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
@Eric-Arellano
Copy link
Contributor Author

Bump @benjyw.

Also, I think we should add this to protobuf_sources, shell_sources, shunit2_sources, files, and resources? It's a little less useful for those (especially files and resources) because you don't need to override metadata at a file-level basis there fwict, but I think it makes sense for consistency.

Stretch goal would be getting the old-style macros python_requirements etc to support it, which actually would be extremely useful. It's common that users need to change the dependencies for a dep to include setuptools.

Copy link
Contributor

@benjyw benjyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I'd like @stuhood to get a chance to look before merging.

@@ -670,13 +698,37 @@ class PythonSourcesGeneratingSourcesField(PythonGeneratingSourcesBase):
)


class PythonSourcesOverridesField(OverridesField):
help = (
"Override the field values for generated `python_source` targets.\n\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you checked how this will render on the docsite?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked with ./pants help-all and it looks good. I care a ton about how help messages render, which is why I always pester people to add \n\n.

How would I check on the docsite? The dry-run only does markdown, right?

value_or_default = super().compute_value(raw_value, address)
if value_or_default is None:
return None
invalid_type_exception = InvalidFieldTypeException(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to instantiate an exception you may never use. Maybe have this be a function?

[ci skip-rust]
[ci skip-build-wheels]
@Eric-Arellano
Copy link
Contributor Author

but I'd like @stuhood to get a chance to look before merging.

Given that Stu is OOO for another day, going to merge. But based on his feedback in #13187 (comment) and in private conversation, it sounds like Stu is more concerned about 1:1:1 rather than the generic overrides mechanism:

today's discussion has made it a bit clearer for me that the bit that I object to is using overrides with recursive target generation, rather than overrides themselves.

This PR does not change 1:1:1 at all. It actually makes 1:1:1 much more viable (one target generator per directory). I'm holding off on #13187 because we're not certain if we should use 1:1:1 with Go or not, but here, seems uncontroversial for Python.

I'm happy to address specific followups Stu has.

@stuhood
Copy link
Member

stuhood commented Oct 20, 2021

but I'd like @stuhood to get a chance to look before merging.

Given that Stu is OOO for another day, going to merge. But based on his feedback in #13187 (comment) and in private conversation, it sounds like Stu is more concerned about 1:1:1 rather than the generic overrides mechanism:

today's discussion has made it a bit clearer for me that the bit that I object to is using overrides with recursive target generation, rather than overrides themselves.

This PR does not change 1:1:1 at all. It actually makes 1:1:1 much more viable (one target generator per directory). I'm holding off on #13187 because we're not certain if we should use 1:1:1 with Go or not, but here, seems uncontroversial for Python.

I'm happy to address specific followups Stu has.

Yep: this all makes sense, and I'm fine with adding overrides.

The real discussion that needs to continue is around 1:1:1.

Eric-Arellano added a commit that referenced this pull request Oct 20, 2021
…tobuf_sources` (#13298)

See #13270.

This does not yet add the field to `java_sources` and `junit_tests`, tracked by #13297.

I'm not sure if we should add this mechanism to `files` and `resources`? I'm having a hard time thinking of when you would want to override the metadata for a single file. But, we might want to do it for the sake of consistency?

This also does not hook up `python_requirements` and `poetry_requirements` because those use the old CAOF macro system, rather than the Target API.

[ci skip-rust]
Eric-Arellano added a commit that referenced this pull request Oct 21, 2021
…o new target generator `python_test_utils`, not `python_tests` (#13299)

Part 1 of #13238.

## Why we need to fix this

Currently, `python_tests` includes `conftest.py` and `*_test.pyi` in its `sources` field, and it generates `python_test` targets for those. We special case our Pytest code to ignore running tests on those generated targets:

https://github.com/pantsbuild/pants/blob/e8654186c11c87e372bd62ab4f82f39ad2f5b208/src/python/pants/backend/python/subsystems/pytest.py#L50-L57

This will not work with lockfiles #13238: `conftest.py` and `*_test.pyi` both must be modeled by a `python_source` target, not `python_test`. A `python_test` target has the field `resolve: str`, whereas `python_source` has `compatible_resolves: list[str]`.  The `resolve` says which exact resolve to use, and then we validate the transitive closure is compatible with that one. Because a `conftest.py` can be a dependency of many `python_test` targets, it's important that it participates in this `compatible_resolves` mechanism.

## Solution

Add a new `python_test_utils` target generator, which generates `python_source` targets. It behaves identically to the `python_sources`  target generator, except that it has the default `sources` of `conftest.py` and `*_test.pyi`.

Why a new target that behaves identically to `python_sources`? This modeling reduces the risk of folks unintentionally including test support files like `conftest.py` in production, such as in a `pex_binary` and `python_distribution`. For `python_distribution`, it's very common to add a dependency on a `python_sources` target generator, rather than a generated `python_source` target, i.e. `:lib` instead of `foo.py:lib`. We want to avoid the gotcha of `:lib` including `conftest.py` because that's how the default `sources` work, and we don't want users to have to be aware of this gotcha and to do set arithmetic in the `sources` fields of two `python_sources` to make sure there aren't duplicate owners of the same `conftest.py`.

A new target also dramatically minimizes the breaking changes of this PR. Now, the risk is that some files will be unowned, whereas before there was the risk that they'd be owned by a different target generator than before, so might inherit different metadata like `dependencies` and might be included in unexpected places like a `python_distribution` depending on `:lib`.

--

This PR does not deprecate the special casing in `pytest.py` that allows for a `python_test` target to be used with `conftest.py` and `*_test.pyi`. That special casing will be deprecated in a followup.

## Alternatives considered

### `python_tests` generates `python_source` targets

This is technically feasible, but really clunky and confusing to document. For example, the `conftest.py` `python_source` target would presumably default to the global `compatible_resolves` option (configured in `[python-setup]`). To change it, we'd either need to add a field to `python_tests` like `conftest_compatible_resolves`, or the user would have to know to use the `overrides` mechanism from #13270. Not intuitive!

### Deprecating having a default `sources` field for `python_tests` and `python_sources`

Users would have to either explicitly use the new defaults or stick to the old defaults. Once we're confident people aren't using a default, we can safely change it without breaking anyone.

However, this would require a ludicrous amount of churn for a problem that most users don't even have. Even if we automated this change with `./pants update-build-files`, it is still extremely disruptive.

Also, this isn't very feasible because of how we validate that each glob in the `sources` field matches. When it's the field's `default`, we only require _any_ glob to match. When it's explicitly declared in a BUILD file, we require _all_ globs to match. So, a user could not safely set `["*_test.py", "test_*.py", "tests.py"]` in a BUILD file unless they had all those files, which is unlikely.

### A global option to change the default

I don't think this is technically feasible. The Target API is intentionally shielded from the Rules API. There's no way for an option to change the default of a field. It would require a ludicrous amount of special casing in `engine/internals/graph.py` etc to have our Rules code that consumes these fields to change behavior of things like hydrating sources based on the option.


## Impact

Our dependency inference rule for `conftest.py` requires that there be an owning target:

https://github.com/pantsbuild/pants/blob/a9af04dc94de2f718aae5b8ce488faaf8c39c07e/src/python/pants/backend/python/dependency_inference/rules.py#L231-L235

So,, unless you have explicitly put `conftest.py` into a `python_sources` or `python_tests` target's `sources` field, then you'll get this error when upgrading to Pants 2.8:

```
pants.base.exceptions.ResolveError: No owning targets could be found for the file `src/python/pants/conftest.py`.

Please check that there is a BUILD file in the parent directory src/python/pants with a target whose `sources` field includes the file. See https://www.pantsbuild.org/v2.8/docs/targets for more information on target definitions.

You may want to run `./pants tailor` to autogenerate your BUILD files. See https://www.pantsbuild.org/v2.8/docs/create-initial-build-files.
```

It violates our deprecation policy to error here, but we don't think there's a way to gracefully deprecate. Hopefully folks will run `./pants tailor` after encountering this and get `python_test_utils` targets added.

When a new `python_test_utils` target is added, the user may need to update its metadata like its `dependencies` to work properly. That might not be very intuitive, so we should document it in upgrade notes. Fortunately, the risk is mostly that users' tests will fail: there is far less risk this will break production code.

[ci skip-rust]
[ci skip-build-wheels]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants