def test_raw_specs_without_file_owners_do_not_exist( rule_runner: RuleRunner) -> None: rule_runner.write_files({ "real/f.txt": "", "real/BUILD": "target(sources=['f.txt'])", "empty/BUILD": "# empty" }) def assert_resolve_error(spec: Spec, *, expected: str) -> None: with engine_error(contains=expected): resolve_raw_specs_without_file_owners(rule_runner, [spec]) def assert_does_not_error(spec: Spec, *, ignore_nonexistent: bool = False) -> None: assert not resolve_raw_specs_without_file_owners( rule_runner, [spec], ignore_nonexistent=ignore_nonexistent) # Literal addresses require for the target to be resolved. assert_resolve_error(AddressLiteralSpec("fake", "tgt"), expected="'fake' does not exist on disk") assert_resolve_error( AddressLiteralSpec("fake/f.txt", "tgt"), expected="'fake/f.txt' does not exist on disk", ) did_you_mean = ResolveError.did_you_mean(bad_name="fake_tgt", known_names=["real"], namespace="real") assert_resolve_error(AddressLiteralSpec("real", "fake_tgt"), expected=str(did_you_mean)) assert_resolve_error(AddressLiteralSpec("real/f.txt", "fake_tgt"), expected=str(did_you_mean)) assert_resolve_error(DirGlobSpec("fake"), expected='Unmatched glob from tests: "fake/*"') assert_does_not_error(DirGlobSpec("empty")) assert_does_not_error(DirGlobSpec("fake"), ignore_nonexistent=True) assert_resolve_error(DirLiteralSpec("fake"), expected='Unmatched glob from tests: "fake/*"') assert_does_not_error(DirLiteralSpec("empty")) assert_does_not_error(DirLiteralSpec("fake"), ignore_nonexistent=True) assert_resolve_error(RecursiveGlobSpec("fake"), expected='Unmatched glob from tests: "fake/**"') assert_does_not_error(RecursiveGlobSpec("empty")) assert_does_not_error(RecursiveGlobSpec("fake"), ignore_nonexistent=True) assert_resolve_error(AncestorGlobSpec("fake"), expected='Unmatched glob from tests: "fake/*"') assert_does_not_error(AncestorGlobSpec("empty")) assert_does_not_error(AncestorGlobSpec("fake"), ignore_nonexistent=True)
def test_raw_specs_without_file_owners_deduplication( rule_runner: RuleRunner) -> None: """When multiple specs cover the same address, we should deduplicate to one single Address.""" rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ file_generator(sources=['f.txt']) nonfile_generator(name="nonfile") """), }) specs = [ AddressLiteralSpec("demo"), DirLiteralSpec("demo"), DirGlobSpec("demo"), RecursiveGlobSpec("demo"), AncestorGlobSpec("demo"), AddressLiteralSpec("demo", target_component="nonfile", generated_component="gen"), AddressLiteralSpec("demo/f.txt"), ] assert resolve_raw_specs_without_file_owners(rule_runner, specs) == [ Address("demo"), Address("demo", target_name="nonfile"), Address("demo", target_name="nonfile", generated_name="gen"), Address("demo", relative_file_path="f.txt"), ]
async def find_all_targets(_: AllTargetsRequest) -> AllTargets: tgts = await Get( Targets, RawSpecsWithoutFileOwners( recursive_globs=(RecursiveGlobSpec(""), ), description_of_origin="the `AllTargets` rule"), ) return AllTargets(tgts)
def test_resolve_with_a_jar(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "BUILD": textwrap.dedent("""\ jvm_artifact( name="jeremy", group="jeremy", artifact="jeremy", version="4.13.2", jar="jeremy.jar", ) """), "jeremy.jar": "hello dave", }) targets = rule_runner.request(Targets, [ RawSpecs(recursive_globs=(RecursiveGlobSpec(""), ), description_of_origin="tests") ]) jeremy_target = targets[0] jar_field = jeremy_target[JvmArtifactJarSourceField] requirement = ArtifactRequirement( coordinate=Coordinate( group="jeremy", artifact="jeremy", version="4.13.2", ), jar=jar_field, ) resolved_lockfile = rule_runner.request( CoursierResolvedLockfile, [ArtifactRequirements([requirement])], ) coordinate = requirement.coordinate assert resolved_lockfile == CoursierResolvedLockfile( entries=(CoursierLockfileEntry( coord=Coordinate(group=coordinate.group, artifact=coordinate.artifact, version=coordinate.version), file_name= f"{coordinate.group}_{coordinate.artifact}_{coordinate.version}.jar", direct_dependencies=Coordinates([]), dependencies=Coordinates([]), file_digest=FileDigest( fingerprint= "55b9afa8d7776cd6c318eec51f506e9c7f66c247dcec343d4667f5f269714f86", serialized_bytes_length=10, ), pants_address=jar_field.address.spec, ), ))
def test_get_target_data(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "foo/BUILD": dedent("""\ target(name="bar", dependencies=[":baz"]) files(name="baz", sources=["*.txt"]) """), "foo/a.txt": "", "foo/b.txt": "", }) tds = rule_runner.request( TargetDatas, [ RawSpecs(recursive_globs=(RecursiveGlobSpec("foo"), ), description_of_origin="tests") ], ) assert list(tds) == [ TargetData( GenericTarget({"dependencies": [":baz"]}, Address("foo", target_name="bar")), None, ("foo/a.txt:baz", "foo/b.txt:baz"), ), TargetData( FilesGeneratorTarget({"sources": ["*.txt"]}, Address("foo", target_name="baz")), ("foo/a.txt", "foo/b.txt"), ("foo/a.txt:baz", "foo/b.txt:baz"), ), TargetData( FileTarget({"source": "a.txt"}, Address("foo", relative_file_path="a.txt", target_name="baz")), ("foo/a.txt", ), (), ), TargetData( FileTarget({"source": "b.txt"}, Address("foo", relative_file_path="b.txt", target_name="baz")), ("foo/b.txt", ), (), ), ]
def parse_spec(self, spec: str) -> tuple[Spec, bool]: """Parse the given spec string and also return `true` if it's an ignore. :raises: CmdLineSpecParser.BadSpecError if the address selector could not be parsed. """ is_ignore = False if spec.startswith("-"): is_ignore = True spec = spec[1:] ( ( path_component, target_component, generated_component, parameters, ), wildcard, ) = native_engine.address_spec_parse(spec) if wildcard == "::": return RecursiveGlobSpec( directory=self._normalize_spec_path(path_component)), is_ignore if wildcard == ":": return DirGlobSpec( directory=self._normalize_spec_path(path_component)), is_ignore if target_component or generated_component or parameters: return ( AddressLiteralSpec( path_component=self._normalize_spec_path(path_component), target_component=target_component, generated_component=generated_component, parameters=FrozenDict(sorted(parameters)), ), is_ignore, ) if "*" in path_component: return FileGlobSpec(spec), is_ignore if PurePath(spec).suffix: return FileLiteralSpec(self._normalize_spec_path(spec)), is_ignore spec_path = self._normalize_spec_path(spec) if spec_path == ".": return DirLiteralSpec(""), is_ignore # Some paths that look like dirs can actually be files without extensions. if Path(self._root_dir, spec_path).is_file(): return FileLiteralSpec(spec_path), is_ignore return DirLiteralSpec(spec_path), is_ignore
def run(enable_resolves: bool) -> ExportResults: rule_runner.set_options( [ f"--python-interpreter-constraints=['=={current_interpreter}']", "--python-resolves={'a': 'lock.txt', 'b': 'lock.txt'}", f"--python-enable-resolves={enable_resolves}", # Turn off lockfile validation to make the test simpler. "--python-invalid-lockfile-behavior=ignore", ], env_inherit={"PATH", "PYENV_ROOT"}, ) targets = rule_runner.request( Targets, [ RawSpecs(recursive_globs=(RecursiveGlobSpec("src/foo"), ), description_of_origin="tests") ], ) all_results = rule_runner.request(ExportResults, [ExportVenvsRequest(targets)]) for result in all_results: assert len(result.post_processing_cmds) == 2 ppc0 = result.post_processing_cmds[0] assert ppc0.argv[1:] == ( # The first arg is the full path to the python interpreter, which we # don't easily know here, so we ignore it in this comparison. os.path.join("{digest_root}", ".", "pex"), os.path.join("{digest_root}", "requirements.pex"), "venv", "--pip", "--collisions-ok", "--remove=all", f"{{digest_root}}/{current_interpreter}", ) assert ppc0.extra_env == FrozenDict({"PEX_MODULE": "pex.tools"}) ppc1 = result.post_processing_cmds[1] assert ppc1.argv == ( "rm", "-f", os.path.join("{digest_root}", ".", "pex"), ) assert ppc1.extra_env == FrozenDict() return all_results
def test_filtered_targets(rule_runner: RuleRunner) -> None: rule_runner.write_files({ "addr_specs/f1.txt": "", "addr_specs/f2.txt": "", "addr_specs/BUILD": dedent("""\ file_generator( sources=["*.txt"], tags=["a"], overrides={"f2.txt": {"tags": ["b"]}}, ) nonfile_generator(name="nonfile", tags=["b"]) target(name='t', tags=["a"]) """), "fs_specs/f1.txt": "", "fs_specs/f2.txt": "", "fs_specs/BUILD": dedent("""\ file_generator( sources=["*.txt"], tags=["a"], overrides={"f2.txt": {"tags": ["b"]}}, ) target(name='t', sources=["f1.txt"], tags=["a"]) """), }) specs = RawSpecs( recursive_globs=(RecursiveGlobSpec("addr_specs"), ), file_globs=(FileGlobSpec("fs_specs/*.txt"), ), filter_by_global_options=True, description_of_origin="tests", ) def check(tags_option: str | None, expected: set[Address]) -> None: if tags_option: rule_runner.set_options([f"--tag={tags_option}"]) addresses = rule_runner.request(Addresses, [specs]) result = rule_runner.request(FilteredTargets, [addresses]) assert {t.address for t in result} == expected addr_f1 = Address("addr_specs", relative_file_path="f1.txt") addr_f2 = Address("addr_specs", relative_file_path="f2.txt") addr_gen = Address("addr_specs", target_name="nonfile", generated_name="gen") addr_direct = Address("addr_specs", target_name="t") fs_f1 = Address("fs_specs", relative_file_path="f1.txt") fs_f2 = Address("fs_specs", relative_file_path="f2.txt") fs_direct = Address("fs_specs", target_name="t") all_a_tags = {addr_f1, addr_direct, fs_f1, fs_direct} all_b_tags = {addr_gen, addr_f2, fs_f2} check(None, {*all_a_tags, *all_b_tags}) check("a", all_a_tags) check("b", all_b_tags) check("-a", all_b_tags) check("-b", all_a_tags)
def test_raw_specs_without_file_owners_parametrize( rule_runner: RuleRunner, ) -> None: rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ file_generator(sources=['f.txt'], resolve=parametrize("a", "b")) nonfile_generator(name="nonfile", resolve=parametrize("a", "b")) target(sources=['f.txt'], name="not_gen", resolve=parametrize("a", "b")) """), }) def assert_resolved(spec: Spec, expected: set[Address]) -> None: assert set(resolve_raw_specs_without_file_owners(rule_runner, [spec])) == expected not_gen_resolve_a = Address("demo", target_name="not_gen", parameters={"resolve": "a"}) not_gen_resolve_b = Address("demo", target_name="not_gen", parameters={"resolve": "b"}) file_generator_resolve_a = { Address("demo", relative_file_path="f.txt", parameters={"resolve": "a"}), Address("demo", parameters={"resolve": "a"}), } file_generator_resolve_b = { Address("demo", relative_file_path="f.txt", parameters={"resolve": "b"}), Address("demo", parameters={"resolve": "b"}), } nonfile_generator_resolve_a = { Address("demo", target_name="nonfile", generated_name="gen", parameters={"resolve": "a"}), Address("demo", target_name="nonfile", parameters={"resolve": "a"}), } nonfile_generator_resolve_b = { Address("demo", target_name="nonfile", generated_name="gen", parameters={"resolve": "b"}), Address("demo", target_name="nonfile", parameters={"resolve": "b"}), } assert_resolved( RecursiveGlobSpec(""), { *file_generator_resolve_a, *file_generator_resolve_b, *nonfile_generator_resolve_a, *nonfile_generator_resolve_b, not_gen_resolve_a, not_gen_resolve_b, }, ) # A literal address for a parameterized target works as expected. assert_resolved( AddressLiteralSpec("demo", target_component="not_gen", parameters=FrozenDict({"resolve": "a"})), {not_gen_resolve_a}, ) assert_resolved( AddressLiteralSpec("demo/f.txt", parameters=FrozenDict({"resolve": "a"})), { Address("demo", relative_file_path="f.txt", parameters={"resolve": "a"}) }, ) assert_resolved( AddressLiteralSpec("demo", "nonfile", generated_component="gen", parameters=FrozenDict({"resolve": "a"})), { Address("demo", target_name="nonfile", generated_name="gen", parameters={"resolve": "a"}) }, ) assert_resolved( # A direct reference to the parametrized target generator. AddressLiteralSpec("demo", parameters=FrozenDict({"resolve": "a"})), {Address("demo", parameters={"resolve": "a"})}, ) # A literal address for a parametrized template should be expanded with the matching targets. assert_resolved( AddressLiteralSpec("demo", target_component="not_gen"), {not_gen_resolve_a, not_gen_resolve_b}, ) # The above affordance plays nicely with target generation. assert_resolved( # Note that this returns references to the two target generators. Certain goals like # `test` may then later replace those with their generated targets. AddressLiteralSpec("demo"), {Address("demo", parameters={"resolve": r}) for r in ("a", "b")}, ) assert_resolved( AddressLiteralSpec("demo", "nonfile", "gen"), { Address("demo", target_name="nonfile", generated_name="gen", parameters={"resolve": r}) for r in ("a", "b") }, ) assert_resolved( AddressLiteralSpec("demo/f.txt"), { Address( "demo", relative_file_path="f.txt", parameters={"resolve": r}) for r in ("a", "b") }, ) # Error on invalid targets. def assert_errors(spec: AddressLiteralSpec) -> None: with engine_error(ValueError): resolve_raw_specs_without_file_owners(rule_runner, [spec]) assert_errors( AddressLiteralSpec("demo", parameters=FrozenDict({"fake": "v"}))) assert_errors( AddressLiteralSpec("demo", parameters=FrozenDict({"resolve": "fake"})))
def test_raw_specs_without_file_owners_literals_vs_globs( rule_runner: RuleRunner) -> None: rule_runner.write_files({ "demo/BUILD": dedent("""\ file_generator(sources=['**/*.txt']) nonfile_generator(name="nonfile") """), "demo/f1.txt": "", "demo/f2.txt": "", "demo/f[3].txt": "", "demo/subdir/f.txt": "", "demo/subdir/f.another_ext": "", "demo/subdir/BUILD": "target(name='another_ext', sources=['f.another_ext'])", "another_dir/BUILD": "target(sources=[])", }) def assert_resolved(spec: Spec, expected: set[Address]) -> None: result = resolve_raw_specs_without_file_owners(rule_runner, [spec]) assert set(result) == expected # Literals should be "one-in, one-out". assert_resolved(AddressLiteralSpec("demo"), {Address("demo")}) assert_resolved(AddressLiteralSpec("demo/f1.txt"), {Address("demo", relative_file_path="f1.txt")}) assert_resolved( AddressLiteralSpec("demo", target_component="nonfile", generated_component="gen"), {Address("demo", target_name="nonfile", generated_name="gen")}, ) assert_resolved( AddressLiteralSpec("demo/subdir", target_component="another_ext"), {Address("demo/subdir", target_name="another_ext")}, ) demo_dir_generated_targets = { Address("demo", relative_file_path="f1.txt"), Address("demo", relative_file_path="f2.txt"), Address("demo", relative_file_path="f[[]3].txt"), Address("demo", target_name="nonfile", generated_name="gen"), } demo_subdir_generated_targets = { Address("demo", relative_file_path="subdir/f.txt"), Address("demo/subdir", target_name="another_ext"), } # `DirGlobSpec` matches all targets that "reside" in the directory, either because explicitly # declared there or generated into that dir. assert_resolved( # Note that this does not include `demo/subdir/f2.ext:../demo`, even though its target # generator matches. DirGlobSpec("demo"), { Address("demo"), Address("demo", target_name="nonfile"), *demo_dir_generated_targets }, ) assert_resolved( # Should include all generated targets that reside in `demo/subdir`, even though their # target generator is in an ancestor. DirGlobSpec("demo/subdir"), demo_subdir_generated_targets, ) # `DirLiteralSpec` matches all targets that "reside" in the directory, but it filters out # target generators. assert_resolved(DirLiteralSpec("demo"), demo_dir_generated_targets) assert_resolved(DirLiteralSpec("demo/subdir"), demo_subdir_generated_targets) all_tgts_in_demo = { Address("demo"), Address("demo", target_name="nonfile"), *demo_dir_generated_targets, *demo_subdir_generated_targets, } assert_resolved(RecursiveGlobSpec("demo"), all_tgts_in_demo) assert_resolved(AncestorGlobSpec("demo/subdir"), all_tgts_in_demo) assert_resolved( AncestorGlobSpec("demo"), { Address("demo"), Address("demo", target_name="nonfile"), Address("demo", target_name="nonfile", generated_name="gen"), Address("demo", relative_file_path="f1.txt"), Address("demo", relative_file_path="f2.txt"), Address("demo", relative_file_path="f[[]3].txt"), }, )
def recursive_glob(directory: str) -> RecursiveGlobSpec: return RecursiveGlobSpec(directory)
def test_recursive_glob() -> None: spec = RecursiveGlobSpec("dir/subdir") assert spec.to_glob() == "dir/subdir/**" assert spec.matches_target_residence_dir("") is False assert spec.matches_target_residence_dir("dir") is False assert spec.matches_target_residence_dir("dir/subdir") is True assert spec.matches_target_residence_dir("dir/subdir/nested") is True assert spec.matches_target_residence_dir("dir/subdir/nested/again") is True assert spec.matches_target_residence_dir("another/subdir") is False assert_build_file_globs( RawSpecsWithoutFileOwners(recursive_globs=(spec,), description_of_origin="tests"), expected_build_globs={"BUILD", "dir/BUILD", "dir/subdir/BUILD", "dir/subdir/**/BUILD"}, expected_validation_globs={"dir/subdir/**"}, ) spec = RecursiveGlobSpec("") assert spec.to_glob() == "**" assert spec.matches_target_residence_dir("") is True assert spec.matches_target_residence_dir("dir") is True assert spec.matches_target_residence_dir("another_dir") is True assert_build_file_globs( RawSpecsWithoutFileOwners(recursive_globs=(spec,), description_of_origin="tests"), expected_build_globs={"BUILD", "**/BUILD"}, expected_validation_globs={"**"}, )