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

Code Quality Tool Lib #20135

Merged
merged 60 commits into from
Nov 15, 2023
Merged

Code Quality Tool Lib #20135

merged 60 commits into from
Nov 15, 2023

Conversation

gauthamnair
Copy link
Contributor

@gauthamnair gauthamnair commented Nov 1, 2023

This is a library supporting the behavior proposed in #17729 (comment)

The usage example is a bit awkward but there is a lot of variation and discussion about the steps to make the use-case smooth. That work is separated out to a subsequent PR. The core is here.

Goal

Allows a user to define custom linters or formatters by:

  1. defining an adhoc-tool compatible target
  2. wrapping it in a new code_quality_tool target
  3. referring to this target from a thin in-repo plugin.

Example Use-case

in pants.toml:

[GLOBAL]
pythonpath = ["%(buildroot)s/pants-plugins"]
backend_packages.add = [

    # needed for python_requirement
    "pants.backend.python",

    # code_quality_tool's target and rules are defined in the adhoc backend
    "pants.backend.experimental.adhoc",

    # thin in-repo plugin activates a particular target as a linter
    "flake8_linter_plugin",
]

in BUILD:

# a python requirement is an adhoc_tool-compatible runnable
python_requirement( 
    name="flake8",
    requirements=["flake8==5.0.4"]
)

# new target type describes how to use a runnable as a code quality tool
code_quality_tool(
    name="flake8_tool",
    runnable=":flake8",
    execution_dependencies=[":flake8_conf"],
    file_glob_include=["**/*.py"],
    file_glob_exclude=["dont_lint_me/**"],
    args=["--indent-size=2"],
)

and an in-repo plugin pants-plugins/flake8_linter_plugin/register.py

from pants.backend.adhoc.code_quality_tool import CodeQualityToolRuleBuilder

def rules():
    cfg = CodeQualityToolRuleBuilder(
            goal="lint", target="//:flake8_tool", name="Flake8", scope="flake8_tool"
    )
    return cfg.rules()

The lib supports:

  • fmt, lint and fix
  • any runnable supported by adhoc_tool
  • passing additional runnables that need to be available on the PATH
  • passing configuration via exec dependencies
  • passing args
  • file-based lint/fmt/fix only (as opposed to target based - therefore no target-level skip_ fields)
  • skip and only working similarly to regular code quality tools.

See the tests for demos.

Full demo repo

See this branch:
https://github.com/gauthamnair/example-pants-byo-tool/tree/code-quality-tool-lib-demo

Follow-ups

@gauthamnair
Copy link
Contributor Author

@huonw @kaos I think I've added the missing ingredients.

  • package has been shuffled around and is simplified and contained in adhoc
  • fix works now
  • Repaired some docs issue (turns out the target had a broken help - how do we usually test for that?)
  • I confirmed that the runtime graph looks fine for this thing despite the dynamic generation.

Copy link
Contributor

@lilatomic lilatomic left a comment

Choose a reason for hiding this comment

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

Really neat!
I left a few comments. I think we're good to go without them. They're mostly possibilities for improvement.
There's some duplication between the _build_*_rules. I think that's fine. I think it's better to have 3 easy-to-follow Transaction Scripts than a pile of small "reusable" components (like constructing the FileSpecMatcher).


One thing which we should think about is rule deduplication. Some tools support multiple operations. For example, https://radon.readthedocs.io/en/latest/ has 4 different metrics. If you set up 4 different CodeQualityTools to achieve this, I think each one will generate a separate CodeQualityToolInstance. I'm not sure how much of a problem that will be, since I think that the runnable target will deduplicate the hard work of installing the tool which is where most of the gains would be. Deduplicating isn't that hard, Metalint and other places in Pants do this with a simple cache decorator.

src/python/pants/backend/code_quality_tool/lib.py Outdated Show resolved Hide resolved
default = ()


class CodeQualityToolRunnableField(StringField):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, agreed, there is some factoring of commonality that needs to be done, which I can take a crack at after these prs are in.

src/python/pants/backend/code_quality_tool/lib.py Outdated Show resolved Hide resolved
src/python/pants/backend/adhoc/code_quality_tool.py Outdated Show resolved Hide resolved
Copy link
Member

@kaos kaos left a comment

Choose a reason for hiding this comment

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

lgtm. Thanks for iterating on this! 🚀

Copy link
Contributor

@huonw huonw left a comment

Choose a reason for hiding this comment

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

I like it, a few things to tweak, but getting very close!

@gauthamnair
Copy link
Contributor Author

@huonw I think the issues you brought up are addressed now.

@benjyw
Copy link
Sponsor Contributor

benjyw commented Nov 15, 2023

Checking in on the status of this. @huonw looks like it needs a last pass over the response to your code review comments?

Copy link
Contributor

@huonw huonw left a comment

Choose a reason for hiding this comment

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

Nice!

@kaos kaos merged commit 5cb1a91 into pantsbuild:main Nov 15, 2023
24 checks passed
huonw pushed a commit that referenced this pull request Dec 30, 2023
Before moving to step 2 of the plan described in
#17729 (comment)
, cleaning up a gross duplication of rule code that I introduced in
#20135 between `adhoc_tool` and
the new `code_quality_tool`.

This PR extracts the shared logic into the concept of a ToolRunner and a
rule to hydrate it in `adhoc_process_support`.

Both `adhoc_tool` and `code_quality_tool` have the latent idea of a tool
runner and a considerable machinery to build it. Starting from something
like
```python
@DataClass(frozen=True)
class ToolRunnerRequest:
    runnable_address_str: str
    args: tuple[str, ...]
    execution_dependencies: tuple[str, ...]
    runnable_dependencies: tuple[str, ...]
    target: Target
```
they need to assemble things like locate the actual runnable by str and
figure out what should be its base digest, args, env, etc. and also
co-locate the execution and runnable dependencies. We now capture that
information as a "runner":
```python
@DataClass(frozen=True)
class ToolRunner:
    digest: Digest
    args: tuple[str, ...]
    extra_env: Mapping[str, str]
    append_only_caches: Mapping[str, str]
    immutable_input_digests: Mapping[str, Digest]
```

After this, `adhoc_tool` and `code_quality_tool` diverge in what they do
with it. `adhoc_tool` uses this runner to generate code and
code_quality_tool uses it to run batches of lint/fmt/fix on source
files.

## Food for thought ...

It should not escape our attention that this `ToolRunner` could also be
surfaced as a Target, to be used by `adhoc_tool` and `code_quality_tool`
rather than each specifying all these fields together. It would also
help to reduce confusion when handling all the kinds of 'dependencies'
arguments that `adhoc_tool` takes.
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.

None yet

5 participants