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)
async def infer_terraform_module_dependencies( request: InferTerraformModuleDependenciesRequest, ) -> InferredDependencies: hydrated_sources = await Get(HydratedSources, HydrateSourcesRequest(request.sources_field)) paths = OrderedSet( filename for filename in hydrated_sources.snapshot.files if filename.endswith(".tf") ) result = await Get( ProcessResult, ParseTerraformModuleSources( sources_digest=hydrated_sources.snapshot.digest, paths=tuple(paths), ), ) candidate_spec_paths = [line for line in result.stdout.decode("utf-8").split("\n") if line] # For each path, see if there is a `terraform_module` target at the specified spec_path. candidate_targets = await Get( Targets, RawSpecs( dir_globs=tuple(DirGlobSpec(path) for path in candidate_spec_paths), unmatched_glob_behavior=GlobMatchErrorBehavior.ignore, description_of_origin="the `terraform_module` dependency inference rule", ), ) # TODO: Need to either implement the standard ambiguous dependency logic or ban >1 terraform_module # per directory. terraform_module_addresses = [ tgt.address for tgt in candidate_targets if tgt.has_field(TerraformModuleSourcesField) ] return InferredDependencies(terraform_module_addresses)
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"), ]
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 test_raw_specs_without_file_owners_filter_by_tag( rule_runner: RuleRunner) -> None: rule_runner.set_options(["--tag=+integration"]) all_integration_tgts = [ Address("demo", target_name="b_f"), Address("demo", target_name="b_nf"), Address("demo", target_name="b_nf", generated_name="gen"), Address("demo", target_name="b_f", relative_file_path="f.txt"), ] rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ file_generator(name="a_f", sources=["f.txt"]) file_generator(name="b_f", sources=["f.txt"], tags=["integration"]) file_generator(name="c_f", sources=["f.txt"], tags=["ignore"]) nonfile_generator(name="a_nf") nonfile_generator(name="b_nf", tags=["integration"]) nonfile_generator(name="c_nf", tags=["ignore"]) """), }) assert (resolve_raw_specs_without_file_owners( rule_runner, [DirGlobSpec("demo")]) == all_integration_tgts) # The same filtering should work when given literal addresses, including generated targets and # file addresses. literals_result = resolve_raw_specs_without_file_owners( rule_runner, [ AddressLiteralSpec("demo", "a_f"), AddressLiteralSpec("demo", "b_f"), AddressLiteralSpec("demo", "c_f"), AddressLiteralSpec("demo", "a_nf"), AddressLiteralSpec("demo", "b_nf"), AddressLiteralSpec("demo", "c_nf"), AddressLiteralSpec("demo/f.txt", "a_f"), AddressLiteralSpec("demo/f.txt", "b_f"), AddressLiteralSpec("demo", "a_nf", "gen"), AddressLiteralSpec("demo", "b_nf", "gen"), AddressLiteralSpec("demo", "c_nf", "gen"), ], ) assert literals_result == all_integration_tgts
def test_raw_specs_without_file_owners_filter_by_exclude_pattern( rule_runner: RuleRunner) -> None: rule_runner.set_options(["--exclude-target-regexp=exclude_me.*"]) rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ file_generator(name="exclude_me_f", sources=["f.txt"]) file_generator(name="not_me_f", sources=["f.txt"]) nonfile_generator(name="exclude_me_nf") nonfile_generator(name="not_me_nf") """), }) not_me_tgts = [ Address("demo", target_name="not_me_f"), Address("demo", target_name="not_me_nf"), Address("demo", target_name="not_me_nf", generated_name="gen"), Address("demo", target_name="not_me_f", relative_file_path="f.txt"), ] assert resolve_raw_specs_without_file_owners( rule_runner, [DirGlobSpec("demo")]) == not_me_tgts # The same filtering should work when given literal addresses, including generated targets and # file addresses. literals_result = resolve_raw_specs_without_file_owners( rule_runner, [ AddressLiteralSpec("demo", "exclude_me_f"), AddressLiteralSpec("demo", "exclude_me_nf"), AddressLiteralSpec("demo", "not_me_f"), AddressLiteralSpec("demo", "not_me_nf"), AddressLiteralSpec("demo", "exclude_me_nf", "gen"), AddressLiteralSpec("demo", "not_me_nf", "gen"), AddressLiteralSpec("demo/f.txt", "exclude_me_f"), AddressLiteralSpec("demo/f.txt", "not_me_f"), ], ) assert literals_result == not_me_tgts
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 dir_glob(directory: str) -> DirGlobSpec: return DirGlobSpec(directory)
def test_dir_glob() -> None: spec = DirGlobSpec("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 False assert spec.matches_target_residence_dir("another/subdir") is False assert_build_file_globs( RawSpecsWithoutFileOwners(dir_globs=(spec,), description_of_origin="tests"), expected_build_globs={"BUILD", "dir/BUILD", "dir/subdir/BUILD"}, expected_validation_globs={"dir/subdir/*"}, ) spec = DirGlobSpec("") assert spec.to_glob() == "*" assert spec.matches_target_residence_dir("") is True assert spec.matches_target_residence_dir("dir") is False assert_build_file_globs( RawSpecsWithoutFileOwners(dir_globs=(spec,), description_of_origin="tests"), expected_build_globs={"BUILD"}, expected_validation_globs={"*"}, )
async def determine_main_pkg_for_go_binary( request: GoBinaryMainPackageRequest, ) -> GoBinaryMainPackage: addr = request.field.address if request.field.value: description_of_origin = ( f"the `{request.field.alias}` field from the target {request.field.address}" ) specified_address = await Get( Address, AddressInput, AddressInput.parse( request.field.value, relative_to=addr.spec_path, description_of_origin=description_of_origin, ), ) wrapped_specified_tgt = await Get( WrappedTarget, WrappedTargetRequest(specified_address, description_of_origin=description_of_origin), ) if not wrapped_specified_tgt.target.has_field(GoPackageSourcesField): raise InvalidFieldException( f"The {repr(GoBinaryMainPackageField.alias)} field in target {addr} must point to " "a `go_package` target, but was the address for a " f"`{wrapped_specified_tgt.target.alias}` target.\n\n" "Hint: you should normally not specify this field so that Pants will find the " "`go_package` target for you." ) return GoBinaryMainPackage(wrapped_specified_tgt.target.address) candidate_targets = await Get( Targets, RawSpecs( dir_globs=(DirGlobSpec(addr.spec_path),), description_of_origin="the `go_binary` dependency inference rule", ), ) relevant_pkg_targets = [ tgt for tgt in candidate_targets if tgt.has_field(GoPackageSourcesField) and tgt.residence_dir == addr.spec_path ] if len(relevant_pkg_targets) == 1: return GoBinaryMainPackage(relevant_pkg_targets[0].address) if not relevant_pkg_targets: raise ResolveError( f"The target {addr} requires that there is a `go_package` " f"target defined in its directory {addr.spec_path}, but none were found.\n\n" "To fix, add a target like `go_package()` or `go_package(name='pkg')` to the BUILD " f"file in {addr.spec_path}." ) raise ResolveError( f"There are multiple `go_package` targets for the same directory of the " f"target {addr}: {addr.spec_path}. It is ambiguous what to use as the `main` " "package.\n\n" f"To fix, please either set the `main` field for `{addr} or remove these " "`go_package` targets so that only one remains: " f"{sorted(tgt.address.spec for tgt in relevant_pkg_targets)}" )