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

Write virtualenvs for python tools as part of export #15098

Merged

Conversation

danxmoran
Copy link
Contributor

@danxmoran danxmoran commented Apr 11, 2022

Closes #14837

Still TODO:

  • Add an integration test
  • Add export hook to pytest plugin
  • Figure out why existing test is timing out with these changes

[ci skip-rust]

[ci skip-build-wheels]

Copy link
Contributor

@Eric-Arellano Eric-Arellano left a comment

Choose a reason for hiding this comment

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

Exciting!

Do you know where to go from here? I'm thinking that we could try adding Doc formatter as another hardcoded tool. That will make it a little more obvious how we can de-duplicate things.

Once we have that good to go, we can start to tackle the issue of interpreter constraints like flake8.

src/python/pants/core/goals/export.py Outdated Show resolved Hide resolved
src/python/pants/core/goals/export.py Outdated Show resolved Hide resolved
src/python/pants/backend/python/lint/isort/rules.py Outdated Show resolved Hide resolved
@Eric-Arellano
Copy link
Contributor

Great progress so far!

After looking at what you have, I am pretty sure that we are going to need a dedicated plug-in hook. I was hoping that we can leverage the plug-in hook we already have for generating lock files. But I do not think that is sufficient because it does not have information about how to install from a lock file that already exists. It only tells us how to generate a new lock file.

As briefly discussed over direct message, it is unfortunate to require plug-in authors to implement a new plug-in implementation if they want to integrate with the export goal. But it is a worthy trade-off to have more boilerplate for a much better user experience, given how requested this feature is.

We will also try to make the boiler plate as minimal as possible.

--

To create a new plug-in hook, we are going to use unions: https://www.pantsbuild.org/docs/rules-api-unions

Define a class like ExportPythonToolSentinel. Empty for now, but add @union. This is our inspiration:

@union
class GenerateToolLockfileSentinel:
"""Tools use this as an entry point to say how to generate their tool lockfile.
Each language ecosystem should set up a union member of `GenerateLockfile`, like
`GeneratePythonLockfile`, as explained in that class's docstring.
Then, each tool should subclass `GenerateToolLockfileSentinel` and set up a rule that goes from the
subclass -> the language's lockfile request, e.g. BlackLockfileSentinel ->
GeneratePythonLockfile. Register a union rule for the `GenerateToolLockfileSentinel` subclass.
"""
resolve_name: ClassVar[str]

class IsortLockfileSentinel(GenerateToolLockfileSentinel):
resolve_name = Isort.options_scope
@rule
def setup_isort_lockfile(
_: IsortLockfileSentinel, isort: Isort, python_setup: PythonSetup
) -> GeneratePythonLockfile:
return GeneratePythonLockfile.from_tool(isort, use_pex=python_setup.generate_lockfiles_with_pex)

Similar to the above, we want each rule for each tool to generally only be one line. Note that there are only two things that are different in your isort and Doc formatter rules:

isort_pex = await Get(Pex, PexRequest, isort.to_pex_request())
dest = os.path.join("python", "virtualenvs", "tools", isort.name)

So, we want our plug-in hook for each tool to return back to us a PexRequest and the name of the tool. You will want to create a new data class that can store both of those things. So each rule will look something like ExportIsortSentinel -> ExportPythonTool. Note that each rule does not need to actually use the Get(Pex) part. We only need the PexRequest. They each need to also register a UnionRule.

From there, we can define a rule that goes from ExportPythonTool -> ExportResult. This is basically the existing rule that you already have for both isort and Doc formatter, only made generic.

Finally, we need to call the new plug-in hook. In your rule export_virtualenvs, request the type union membership. Inspect it for ExportPythonToolSentinel. Use Get(ExportResult, ExportPythonToolSentinel, instance) in a comprehension, like this code:

specified_tool_requests = await MultiGet(
Get(WrappedGenerateLockfile, GenerateToolLockfileSentinel, sentinel())
for sentinel in requested_tool_sentinels
)

(that will do some really cool engine magic. You'll go from ExportIsortSentinel -> ExportPythonTool -> ExportResult, without having to explicitly state the middle step.)

@Eric-Arellano
Copy link
Contributor

Huzzah! That looks great! So now you will want to set that plug-in hook up for each python tool we have. You can search for GeneratePythonLockfile - put the new rules next to those ones.

In direct message, you mentioned an import cycle. You will need to delete the hardcoded iSort and doc formatter code from the file you have been using.

I wonder if we actually want to export every tool? There are some, like the Docker parser, which do not seem useful for end-users. What do you think?

--

Most tools will be exactly like you already have. The only tricky things I think will be ones that set interpreter constraints from user code, along w/ plugins:

flake8.to_pex_request(
interpreter_constraints=partition.interpreter_constraints,
extra_requirements=first_party_plugins.requirement_strings,
),

In that case, you can leverage what we already have with generating lock files. Use a line like Get(GeneratePythonLockfile, Flake8LockfileSentinel()). Then, use the interpreter constraints from that type. For the plug-in requirements, simply put Flake8FirstPartyPlugins in the rule signature

@danxmoran danxmoran force-pushed the danxmoran/export-tool-venvs-14837 branch from 237bc37 to 7a711b7 Compare April 13, 2022 17:20
@danxmoran danxmoran changed the title [NOMERGE] WIP Get export nearly working for isort tool. [WIP] Write virtualenvs for python tools as part of export Apr 13, 2022
The final `pex venv` invocation is failing because of path conflicts.
Pushing this commit for early review.

[ci skip-rust]

[ci skip-build-wheels]
Centralize changes in `src/python/pants/core/goals/export.py`.
Had to use an ugly work-around combining export results to make mypy
happy - I expect it'll change in the next commit once a 2nd tool is
added.

[ci skip-rust]

[ci skip-build-wheels]
Nearly all of the code is duplicated from the `isort` export - a
follow-up commit will clean things up once we have enough examples to
identify the common patterns.

[ci skip-rust]

[ci skip-build-wheels]
Still only covering `isort` and `docformatter` for now.

[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
Be more consistent with exports from user code.

[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
Enabling/exposing the hook causes a panic from the rule graph.

[ci skip-rust]

[ci skip-build-wheels]
Rebasing onto the latest `main` seems to have fixed the "Loop subgraph"
panic from the engine.

[ci skip-rust]

[ci skip-build-wheels]
@danxmoran danxmoran force-pushed the danxmoran/export-tool-venvs-14837 branch from 7a711b7 to ee0c6cd Compare April 17, 2022 17:33
The existing test logic is more of a unit test. Rename for clarity since
I'm about to add a real integration test.

[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]
[ci skip-rust]

[ci skip-build-wheels]

import pytest
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleted code here was moved to another file, see the new export_test.py

# 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]
@stuhood
Copy link
Sponsor Member

stuhood commented Apr 19, 2022

Pushed two commits: the latter one actually addresses the issue.

Copy link
Contributor

@Eric-Arellano Eric-Arellano left a comment

Choose a reason for hiding this comment

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

Awesome! This looks ready to merge to me beyond minor review feedback. Thank you 🎉

@@ -50,6 +50,17 @@ def debug_hint(self) -> str | None:
return self.resolve


@union
class ExportPythonToolSentinel:
"""Pythion tools use this as an entry point to say how to export their tool virtualenv."""
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"""Pythion tools use this as an entry point to say how to export their tool virtualenv."""
"""Python tools use this as an entry point to say how to export their tool virtualenv."""

Copy link
Contributor

Choose a reason for hiding this comment

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

It would probably be good to add instructions in a new paragraph. Subclass this class then create a rule going from the sub class to ExportPythonTool. Register the union rule. If the tool is in pantsbuild/pants, update the export test.

dest = os.path.join("python", "virtualenvs", "tools")
pex = await Get(Pex, PexRequest, request.pex_request)

# NOTE: We add a unique-per-tool prefix to the pex_pex path to avoid conflicts when
Copy link
Contributor

Choose a reason for hiding this comment

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

Excellent comment!



@dataclass(frozen=True)
class ExportPythonTool:
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be good to subclass EngineAwareParameter and implement debug_hint() to return self.resolve_name

)
# TODO: We request the `ExportPythonTool` entries independently of the `ExportResult`s because
# inlining the request causes a rule graph issue. Revisit after #11269.
all_export_tool_results = await MultiGet(
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable could probably use a rename. Maybe all export tool requests

name: str
version: str
experimental: bool = False
type: str = "lint"
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using an enum

Copy link
Contributor

Choose a reason for hiding this comment

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

Or, calling it backend prefix might be better. Use none to represent built in

Comment on lines 104 to 105
# Wanted to check the output of calling each tool pex with `--version`, but not all tools
# implement that flag.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment could probably be removed. Or, maybe reword it to say: "Note: not every tool implements --version so this is the best we can get."

if BanditFieldSet.is_applicable(tgt)
}
constraints = InterpreterConstraints(itertools.chain.from_iterable(unique_constraints))

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: unnecessary white space imo since these lines are highly related. There are a couple other places in the PR like this. Feel free to ignore, not a very big deal

@danxmoran danxmoran changed the title [WIP] Write virtualenvs for python tools as part of export Write virtualenvs for python tools as part of export Apr 19, 2022
@danxmoran danxmoran marked this pull request as ready for review April 19, 2022 22:25
[ci skip-rust]

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

Awesome! Thank you so much :) this will go out in the newest dev release for 2.12

@Eric-Arellano Eric-Arellano merged commit 89731b5 into pantsbuild:main Apr 20, 2022
@danxmoran danxmoran deleted the danxmoran/export-tool-venvs-14837 branch April 20, 2022 16:44
@wisechengyi wisechengyi added category:internal CI, fixes for not-yet-released features, etc. category:new feature and removed category:new feature category:internal CI, fixes for not-yet-released features, etc. labels May 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Export tools (black, pytest, ...) to be discover-able by IDE's
4 participants