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

update-build-files: add support for Buildifier formatter #21208

Conversation

AlexTereshenkov
Copy link
Member

@AlexTereshenkov AlexTereshenkov commented Jul 25, 2024

buildifier was added to the list of supported formatters that can be used to format the BUILD files with the update-build-files goal.

It may be helpful if your organization is migrating from Bazel and wants to keep the style of the BUILD files
consistent or if for any other reason you may want to adopt the formatting style that is enforced by buildifier.

The addition of the Buildifier support was added in https://github.com/pantsbuild/pants/pull/16573/files; this PR adds a relevant option to actually let users choose it to format their BUILD files.

The buildifier can be used on its own, but it can also be used in pair with a Python formatter, such as black
or ruff. For instance, you could first run buildifier to sort the target fields alphabetically,
and then run black to keep the style consistent with the rest of the Python code.

E.g. running

$ pants update-build-files --fmt --formatter=buildifier src/python/pants/backend/python/util_rules/BUILD
$ pants update-build-files --fmt --formatter=black src/python/pants/backend/python/util_rules/BUILD

would convert

resource(name="complete_platform_pex_test", source="complete_platform_pex_test.json")

python_sources(
    overrides={
        "local_dists_pep660.py": dict(dependencies=["./scripts/pep660_backend_wrapper.py"]),
        },
    name="foo",
)

to

# adding a trailing comma, putting one argument per line
resource(
    name="complete_platform_pex_test",
    source="complete_platform_pex_test.json",
)

# reorder the fields, putting the name first
python_sources(
    name="foo",
    overrides={
        "local_dists_pep660.py": dict(dependencies=["./scripts/pep660_backend_wrapper.py"]),
    },
)

@AlexTereshenkov AlexTereshenkov marked this pull request as ready for review July 25, 2024 21:59
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.

Thanks for this.

I'm a little bit confused. It looks like the existing src/python/pants/backend/build_files/fmt/buildifier/rules.py code is focused on formatting build files, e.g. its request is called Fmt BuildFiles Request (emphasis mine). That suggests to me that there should already be a way to format build files with buildifier... but I'm probably missing something? Any insight?

Comment on lines 43 to 44
# Note - this function is kept separate because it is invoked from update_build_files.py, but
# not as a rule.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we either invoke it as a rule, or have buildfier_fmt call this function to avoid the duplication?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh of course, this has been now refactored.

Copy link
Member Author

@AlexTereshenkov AlexTereshenkov Jul 26, 2024

Choose a reason for hiding this comment

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

And now I recalled why it was duplicate - there seems to be an issue to have a call to a function which invokes other rules:

E       native_engine.IntrinsicError: 
Call(pants.core.util_rules.external_tool.download_external_tool, 
DownloadedExternalTool) was not detected in your @rule 
body at rule compile time.

This must a Pants architecture constraint. I'll poke around a bit more.

Leaving the duplicate code as is for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha - I think this may be an issue with the new call-by-name syntax rather than the prior version of the engine. Good to know on a go-forward basis, and I think we'll need to take this into account for the other build files formatters.

Copy link
Contributor

@huonw huonw Aug 7, 2024

Choose a reason for hiding this comment

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

Huh, okay.

Happy to land this as is to keep moving forward, but it'd be good to have a follow-up that resolves the duplication... potentially even just deleting the @rule entirely, since it's apparently not used for anything real? (as evidenced by the backend not being wired up/not doing anything)

That said, I believe it is be normal and supported for @rules to call non-@rule async def functions, so if call-by-name doesn't support it, we should know about it and probably fix it, e.g. random example await _get_relevant_source_files:

async def _get_relevant_source_files(
sources: Iterable[SourcesField], with_js: bool = False
) -> SourceFiles:
return await Get(
SourceFiles,
SourceFilesRequest(
sources,
for_sources_types=(PackageJsonSourceField, FileSourceField)
+ ((ResourceSourceField, JSSourceField) if with_js else ()),
enable_codegen=True,
),
)
@rule
async def install_node_packages_for_address(
req: InstalledNodePackageRequest, union_membership: UnionMembership
) -> InstalledNodePackage:
project_env = await Get(NodeJsProjectEnvironment, NodeJSProjectEnvironmentRequest(req.address))
target = project_env.ensure_target()
transitive_tgts = await Get(TransitiveTargets, TransitiveTargetsRequest([target.address]))
source_files = await _get_relevant_source_files(

As part of the follow-up can you investigate more deeply and maybe file a bug?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, Huon. Definitely, let me either do a follow-up PR with either a clean up or refactoring or filing an issue.

Copy link
Contributor

@krishnan-chandra krishnan-chandra left a comment

Choose a reason for hiding this comment

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

Looks good minus Huon's comment - I think what was missing here before was wiring the existing backend up within goals/update_build_files.py; while the backend was created in #16573, it doesn't appear that the Buildifier rules were added to the goal.

@AlexTereshenkov
Copy link
Member Author

That suggests to me that there should already be a way to format build files with buildifier... but I'm probably missing something? Any insight?

@huonw It appears the formatter was added, but not wired. This might have been intentional because it's a bit of a special usage as Buildifier is intended to be used on Starlark, not on Pants BUILD files which may have unsupported syntax.

Comment on lines 43 to 44
# Note - this function is kept separate because it is invoked from update_build_files.py, but
# not as a rule.
# not as a rule. This function cannot be called from the `buildifier_fmt` rule because other rules
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is true, then _run_buildifier_fmt is only used in tests? I think we'll also probably have to figure out another way to run the rules in other formatter backends as well since they follow the same pattern.

Is it possible to invoke the buildifier_fmt rule in the tests directly without needing this helper function?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, it's used in the sources, actually:

result = await _run_buildifier_fmt(

Copy link
Contributor

@krishnan-chandra krishnan-chandra Jul 26, 2024

Choose a reason for hiding this comment

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

I see. Is it possible to make that call invoke the buildifier_fmt rule instead of the helper function, or does that not work either?

If the call-by-name architecture constraint is that rules should not be invoked from non-rule functions, then we should keep the chain consistent and call the right rule from format_build_file_with_buildifier.

Copy link
Member Author

@AlexTereshenkov AlexTereshenkov Jul 26, 2024

Choose a reason for hiding this comment

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

Nope, can't call buildifier_fmt rule from the format_build_file_with_buildifier rule.

  File "/home/username/code/pants/src/python/pants/engine/internals/rule_visitor.py", line 277, in _get_byname_awaitable

    raise parse_error(

pants.engine.internals.selectors.GetParseError: Invalid Get. 
/home/username/code/pants/src/python/pants/core/goals/update_build_files.py:440: 
Expected an `**implicitly(..)` application as the only keyword input. 
Failed for Get() in /home/username/code/pants/src/python/pants/core/goals/update_build_files.py.

@AlexTereshenkov
Copy link
Member Author

@huonw I am sorry I am not sure if you have any feedback for me since the last interaction we had in this PR? No rush, just checking if I have missed something.

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.

Looks good, other than:

  1. the note about follow up
  2. can you update the PR description before merging to reference Add buildifier as a BUILD file formatter #16573 and how this is doing the wiring up that was missed there?

# ------------------------------------------------------------------------------------------


def run_buildifier(
Copy link
Contributor

Choose a reason for hiding this comment

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

Good to have a test 👍

Comment on lines 43 to 44
# Note - this function is kept separate because it is invoked from update_build_files.py, but
# not as a rule.
Copy link
Contributor

@huonw huonw Aug 7, 2024

Choose a reason for hiding this comment

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

Huh, okay.

Happy to land this as is to keep moving forward, but it'd be good to have a follow-up that resolves the duplication... potentially even just deleting the @rule entirely, since it's apparently not used for anything real? (as evidenced by the backend not being wired up/not doing anything)

That said, I believe it is be normal and supported for @rules to call non-@rule async def functions, so if call-by-name doesn't support it, we should know about it and probably fix it, e.g. random example await _get_relevant_source_files:

async def _get_relevant_source_files(
sources: Iterable[SourcesField], with_js: bool = False
) -> SourceFiles:
return await Get(
SourceFiles,
SourceFilesRequest(
sources,
for_sources_types=(PackageJsonSourceField, FileSourceField)
+ ((ResourceSourceField, JSSourceField) if with_js else ()),
enable_codegen=True,
),
)
@rule
async def install_node_packages_for_address(
req: InstalledNodePackageRequest, union_membership: UnionMembership
) -> InstalledNodePackage:
project_env = await Get(NodeJsProjectEnvironment, NodeJSProjectEnvironmentRequest(req.address))
target = project_env.ensure_target()
transitive_tgts = await Get(TransitiveTargets, TransitiveTargetsRequest([target.address]))
source_files = await _get_relevant_source_files(

As part of the follow-up can you investigate more deeply and maybe file a bug?

@AlexTereshenkov AlexTereshenkov merged commit 136eadd into pantsbuild:main Aug 9, 2024
25 checks passed
@AlexTereshenkov AlexTereshenkov deleted the pants/add-buildifer-as-build-file-formatter branch August 9, 2024 12:16
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.

3 participants