Skip to content

Commit

Permalink
turn path/BUILD into path in the tailor goal (pantsbuild#19323)
Browse files Browse the repository at this point in the history
Transforms a request to `pants tailor /path/to/BUILD` into `pants tailor
/path/to` which is probably what people want.

The wrinkle is that if the BUILD file exists, we need to turn a
FileLiteralSpec into DirLiteralSpec.

closes pantsbuild#17868
  • Loading branch information
lilatomic authored Jun 25, 2023
1 parent 2bfccd3 commit 5fb3cf4
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 1 deletion.
49 changes: 48 additions & 1 deletion src/python/pants/core/goals/tailor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
from abc import ABCMeta
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Iterator, Mapping, cast

from pants.base.specs import AncestorGlobSpec, RawSpecs, Specs
from pants.base.specs import AncestorGlobSpec, DirLiteralSpec, RawSpecs, Specs
from pants.build_graph.address import Address
from pants.engine.collection import DeduplicatedCollection
from pants.engine.console import Console
Expand Down Expand Up @@ -571,6 +572,48 @@ def make_content(bf_path: str, pts: Iterable[PutativeTarget]) -> FileContent:
return EditedBuildFiles(new_digest, tuple(sorted(created)), tuple(sorted(updated)))


def spec_with_build_to_dir(spec: RawSpecs, build_file_patterns: tuple[str, ...]) -> RawSpecs:
"""Convert a spec like `path/to/BUILD` into `path/to`, which is probably the intention."""

filespec_matcher = FilespecMatcher(build_file_patterns, ())

def is_build_file(s: str):
return bool(filespec_matcher.matches([s]))

new_file_literals = []
new_dir_literals = []

# handles existing BUILD files
for file_literal in spec.file_literals:
path = Path(file_literal.file)
if is_build_file(path.name):
# convert FileLiteralSpec into DirLiteralSpec
new_dir_literals.append(DirLiteralSpec(path.parent.as_posix()))
else:
new_file_literals.append(file_literal)

# If the BUILD file doesn't exist (possibly because it was deleted)
# it will appear as a dir_literal
for dir_literal in spec.dir_literals:
path = Path(dir_literal.directory)
if is_build_file(path.name):
new_dir_literals.append(DirLiteralSpec(path.parent.as_posix()))
else:
new_dir_literals.append(dir_literal)

return dataclasses.replace(
spec, dir_literals=tuple(new_dir_literals), file_literals=tuple(new_file_literals)
)


def resolve_specs_with_build(specs: Specs, build_file_patterns: tuple[str, ...]) -> Specs:
"""Convert Specs with specs like `path/to/BUILD` into `path/to`, which is probably the
intention."""
new_includes = spec_with_build_to_dir(specs.includes, build_file_patterns)
new_ignores = spec_with_build_to_dir(specs.ignores, build_file_patterns)
return dataclasses.replace(specs, includes=new_includes, ignores=new_ignores)


@goal_rule
async def tailor(
tailor_subsystem: TailorSubsystem,
Expand All @@ -581,6 +624,9 @@ async def tailor(
build_file_options: BuildFileOptions,
) -> TailorGoal:
tailor_subsystem.validate_build_file_name(build_file_options.patterns)

specs = resolve_specs_with_build(specs, build_file_options.patterns)

if not specs:
if not specs.includes.from_change_detection:
logger.warning(
Expand All @@ -593,6 +639,7 @@ async def tailor(
* `{bin_name()} tailor ::` to run on everything
* `{bin_name()} tailor dir::` to run on `dir` and subdirs
* `{bin_name()} tailor dir` to run on `dir`
* `{bin_name()} tailor dir/{tailor_subsystem.build_file_name}` to run on `dir`
* `{bin_name()} --changed-since=HEAD tailor` to only run on changed and new files
"""
)
Expand Down
43 changes: 43 additions & 0 deletions src/python/pants/core/goals/tailor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import pytest

from pants.base.specs import DirGlobSpec, DirLiteralSpec, FileLiteralSpec, RawSpecs, Specs
from pants.core.goals import tailor
from pants.core.goals.tailor import (
AllOwnedSources,
Expand All @@ -25,6 +26,7 @@
default_sources_for_target_type,
has_source_or_sources_field,
make_content_str,
resolve_specs_with_build,
)
from pants.core.util_rules import source_files
from pants.engine.fs import DigestContents, FileContent, PathGlobs, Paths
Expand Down Expand Up @@ -586,3 +588,44 @@ def make_ptgt(path: str, name: str) -> PutativeTarget:
),
)
assert set(result) == set(valid_ptgts)


@pytest.mark.parametrize("build_file_name", ["BUILD", "OTHER_NAME"])
def test_resolve_specs_targetting_build_files(build_file_name) -> None:
specs = Specs(
includes=RawSpecs(
description_of_origin="CLI arguments",
dir_literals=(DirLiteralSpec(f"src/{build_file_name}"), DirLiteralSpec("src/dir")),
dir_globs=(DirGlobSpec("src/other/"),),
file_literals=(FileLiteralSpec(f"src/exists/{build_file_name}.suffix"),),
),
ignores=RawSpecs(
description_of_origin="CLI arguments",
dir_literals=(DirLiteralSpec(f"bad/{build_file_name}"), DirLiteralSpec("bad/dir")),
dir_globs=(DirGlobSpec("bad/other/"),),
file_literals=(FileLiteralSpec(f"bad/exists/{build_file_name}.suffix"),),
),
)
build_file_patterns = (build_file_name, f"{build_file_name}.*")
resolved = resolve_specs_with_build(specs, build_file_patterns)

assert resolved.includes.file_literals == tuple()
assert resolved.ignores.file_literals == tuple()

assert resolved.includes.dir_literals == (
DirLiteralSpec("src/exists"),
DirLiteralSpec("src"),
DirLiteralSpec("src/dir"),
)
assert resolved.ignores.dir_literals == (
DirLiteralSpec("bad/exists"),
DirLiteralSpec("bad"),
DirLiteralSpec("bad/dir"),
)

assert resolved.includes.dir_globs == (
DirGlobSpec("src/other/"),
), "did not passthrough other spec type"
assert resolved.ignores.dir_globs == (
DirGlobSpec("bad/other/"),
), "did not passthrough other spec type"

0 comments on commit 5fb3cf4

Please sign in to comment.