def test_address_specs_filter_by_exclude_pattern( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.set_options( ["--exclude-target-regexp=exclude_me.*"]) address_specs_rule_runner.create_file("demo/f.txt") address_specs_rule_runner.add_to_build_file( "demo", dedent("""\ mock_tgt(name="exclude_me", sources=["f.txt"]) mock_tgt(name="not_me", sources=["f.txt"]) """), ) assert resolve_address_specs( address_specs_rule_runner, [SiblingAddresses("demo")]) == {Address("demo", target_name="not_me")} # The same filtering should work when given literal addresses, including file addresses. # The filtering will operate against the normalized Address.spec. literals_result = resolve_address_specs( address_specs_rule_runner, [ AddressLiteralSpec("demo", "exclude_me"), AddressLiteralSpec("demo", "not_me"), AddressLiteralSpec("demo/f.txt", "exclude_me"), AddressLiteralSpec("demo/f.txt", "not_me"), ], ) assert literals_result == { Address("demo", relative_file_path="f.txt", target_name="not_me"), Address("demo", target_name="not_me"), }
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 test_address_specs_deduplication( address_specs_rule_runner: RuleRunner) -> None: """When multiple specs cover the same address, we should deduplicate to one single AddressWithOrigin. We should use the most specific origin spec possible, such as AddressLiteralSpec > SiblingAddresses. """ address_specs_rule_runner.create_file("demo/f.txt") address_specs_rule_runner.add_to_build_file("demo", "mock_tgt(sources=['f.txt'])") # We also include a file address to ensure that that is included in the result. specs = [ AddressLiteralSpec("demo", "demo"), AddressLiteralSpec("demo/f.txt", "demo"), SiblingAddresses("demo"), DescendantAddresses("demo"), AscendantAddresses("demo"), ] assert resolve_address_specs(address_specs_rule_runner, specs) == { AddressWithOrigin(Address("demo"), AddressLiteralSpec("demo", "demo")), AddressWithOrigin( Address("demo", relative_file_path="f.txt"), AddressLiteralSpec("demo/f.txt", "demo"), ), }
def parse_spec(self, spec: str) -> Union[AddressSpec, FilesystemSpec]: """Parse the given spec into an `AddressSpec` or `FilesystemSpec` object. :raises: CmdLineSpecParser.BadSpecError if the address selector could not be parsed. """ if spec.endswith("::"): spec_path = spec[:-len("::")] return DescendantAddresses( directory=self._normalize_spec_path(spec_path)) if spec.endswith(":"): spec_path = spec[:-len(":")] return SiblingAddresses( directory=self._normalize_spec_path(spec_path)) if ":" in spec: spec_parts = spec.rsplit(":", 1) spec_path = self._normalize_spec_path(spec_parts[0]) return AddressLiteralSpec(path_component=spec_path, target_component=spec_parts[1]) if spec.startswith("!"): return FilesystemIgnoreSpec(spec[1:]) if "*" in spec: return FilesystemGlobSpec(spec) if PurePath(spec).suffix: return FilesystemLiteralSpec(self._normalize_spec_path(spec)) spec_path = self._normalize_spec_path(spec) if Path(self._root_dir, spec_path).is_file(): return FilesystemLiteralSpec(spec_path) # Else we apply address shorthand, i.e. `src/python/pants/util` -> # `src/python/pants/util:util` return AddressLiteralSpec(path_component=spec_path, target_component=PurePath(spec).name)
def test_address_specs_do_not_exist( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.write_files({ "real/f.txt": "", "real/BUILD": "mock_tgt(sources=['f.txt'])", "empty/BUILD": "# empty" }) def assert_resolve_error(specs: Iterable[AddressSpec], *, expected: str) -> None: with pytest.raises(ExecutionError) as exc: resolve_address_specs(address_specs_rule_runner, specs) assert expected in str(exc.value) # Literal addresses require both a BUILD file to exist and for a 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)) # SiblingAddresses requires the BUILD file to exist and at least one match. assert_resolve_error( [SiblingAddresses("fake")], expected= ("'fake' does not contain any BUILD files, but 'fake:' expected matching targets " "there."), ) assert_resolve_error( [SiblingAddresses("empty")], expected="Address spec 'empty:' does not match any targets", ) # MaybeEmptySiblingAddresses does not require a BUILD file to exist nor any matches. assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("fake")]) assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("empty")]) # DescendantAddresses requires at least one match, even if BUILD files exist. assert_resolve_error( [DescendantAddresses("fake"), DescendantAddresses("empty")], expected="Address spec 'fake::' does not match any targets", ) # AscendantAddresses does not require any matches or BUILD files. assert not resolve_address_specs( address_specs_rule_runner, [AscendantAddresses("fake"), AscendantAddresses("empty")])
def test_address_specs_do_not_exist( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.write_files({ "real/f.txt": "", "real/BUILD": "mock_tgt(sources=['f.txt'])", "empty/BUILD": "# empty" }) def assert_resolve_error(specs: Iterable[AddressSpec], *, expected: str) -> None: with engine_error(contains=expected): resolve_address_specs(address_specs_rule_runner, specs) # Literal addresses require for the relevant BUILD file to exist and 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)) # SiblingAddresses requires at least one match. assert_resolve_error( [SiblingAddresses("fake")], expected="No targets found for the address glob `fake:`", ) assert_resolve_error( [SiblingAddresses("empty")], expected="No targets found for the address glob `empty:`") # MaybeEmptySiblingAddresses does not require any matches. assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("fake")]) assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("empty")]) # DescendantAddresses requires at least one match. assert_resolve_error( [DescendantAddresses("fake"), DescendantAddresses("empty")], expected= "No targets found for these address globs: ['empty::', 'fake::']", ) # AscendantAddresses does not require any matches. assert not resolve_address_specs( address_specs_rule_runner, [AscendantAddresses("fake"), AscendantAddresses("empty")])
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 make_target_with_origin(address: Optional[Address] = None) -> TargetWithOrigin: if address is None: address = Address("", target_name="tests") return TargetWithOrigin( MockTarget({}, address=address), origin=AddressLiteralSpec(address.spec_path, address.target_name), )
def parse_spec(self, spec: str) -> AddressSpec | FilesystemSpec: """Parse the given spec into an `AddressSpec` or `FilesystemSpec` object. :raises: CmdLineSpecParser.BadSpecError if the address selector could not be parsed. """ if spec.endswith("::"): spec_path = spec[:-len("::")] return DescendantAddresses( directory=self._normalize_spec_path(spec_path)) if spec.endswith(":"): spec_path = spec[:-len(":")] return SiblingAddresses( directory=self._normalize_spec_path(spec_path)) if ":" in spec or "#" in spec: tgt_parts = spec.split(":", maxsplit=1) path_component = tgt_parts[0] if len(tgt_parts) == 1: target_component = None generated_parts = path_component.split("#", maxsplit=1) if len(generated_parts) == 1: generated_component = None else: path_component, generated_component = generated_parts else: generated_parts = tgt_parts[1].split("#", maxsplit=1) if len(generated_parts) == 1: target_component = generated_parts[0] generated_component = None else: target_component, generated_component = generated_parts return AddressLiteralSpec( path_component=self._normalize_spec_path(path_component), target_component=target_component, generated_component=generated_component, ) if spec.startswith("!"): return FilesystemIgnoreSpec(spec[1:]) if "*" in spec: return FilesystemGlobSpec(spec) if PurePath(spec).suffix: return FilesystemLiteralSpec(self._normalize_spec_path(spec)) spec_path = self._normalize_spec_path(spec) if Path(self._root_dir, spec_path).is_file(): return FilesystemLiteralSpec(spec_path) # Else we apply address shorthand, i.e. `src/python/pants/util` -> # `src/python/pants/util:util` return AddressLiteralSpec(spec_path, None, None)
def test_address_specs_do_not_exist(self) -> None: self.create_file("real/f.txt") self.add_to_build_file("real", "mock_tgt(sources=['f.txt'])") self.add_to_build_file("empty", "# empty") def assert_resolve_error(specs: Iterable[AddressSpec], *, expected: str) -> None: with pytest.raises(ExecutionError) as exc: self.resolve_address_specs(specs) assert expected in str(exc.value) # Literal addresses require both a BUILD file to exist and for a 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)) # SiblingAddresses require the BUILD file to exist, but are okay if no targets are resolved. assert_resolve_error( [SiblingAddresses("fake")], expected= ("'fake' does not contain any BUILD files, but 'fake:' expected matching targets " "there."), ) assert not self.resolve_address_specs([SiblingAddresses("empty")]) # DescendantAddresses requires at least one match, even if BUILD files exist. assert_resolve_error( [DescendantAddresses("fake"), DescendantAddresses("empty")], expected="Address spec 'fake::' does not match any targets", ) # AscendantAddresses does not require any matches or BUILD files. assert not self.resolve_address_specs( [AscendantAddresses("fake"), AscendantAddresses("empty")])
def address_literal( directory: str, name: str | None = None, generated: str | None = None, parameters: dict[str, str] | None = None, ) -> AddressLiteralSpec: return AddressLiteralSpec( directory, name, generated, FrozenDict(sorted(parameters.items()) if parameters else ()))
def calculate_specs( options_bootstrapper: OptionsBootstrapper, options: Options, session: SchedulerSession, *, build_root: Optional[str] = None, ) -> Specs: """Determine the specs for a given Pants run.""" build_root = build_root or get_buildroot() specs = SpecsParser(build_root).parse_specs(options.specs) changed_options = ChangedOptions.from_options(options.for_scope("changed")) logger.debug("specs are: %s", specs) logger.debug("changed_options are: %s", changed_options) if specs.provided and changed_options.provided: changed_name = "--changed-since" if changed_options.since else "--changed-diffspec" if specs.filesystem_specs and specs.address_specs: specs_description = "target and file arguments" elif specs.filesystem_specs: specs_description = "file arguments" else: specs_description = "target arguments" raise InvalidSpecConstraint( f"You used `{changed_name}` at the same time as using {specs_description}. Please " "use only one.") if not changed_options.provided: return specs git = get_git() if not git: raise InvalidSpecConstraint( "The `--changed-*` options are only available if Git is used for the repository." ) changed_request = ChangedRequest( sources=tuple(changed_options.changed_files(git)), dependees=changed_options.dependees, ) (changed_addresses, ) = session.product_request( ChangedAddresses, [Params(changed_request, options_bootstrapper)]) logger.debug("changed addresses: %s", changed_addresses) address_specs = [] for address in cast(ChangedAddresses, changed_addresses): address_input = AddressInput.parse(address.spec) address_specs.append( AddressLiteralSpec( path_component=address_input.path_component, # NB: AddressInput.target_component may be None, but AddressLiteralSpec expects a # string. target_component=address_input.target_component or address.target_name, )) return Specs(AddressSpecs(address_specs, filter_by_global_options=True), FilesystemSpecs([]))
def test_strip_address_origin() -> None: addr = Address.parse("//:demo") result = run_rule_with_mocks( strip_address_origins, rule_args=[ AddressesWithOrigins( [AddressWithOrigin(addr, AddressLiteralSpec("", "demo"))]) ], ) assert list(result) == [addr]
def test_address_specs_deduplication(address_specs_rule_runner: RuleRunner) -> None: """When multiple specs cover the same address, we should deduplicate to one single Address.""" address_specs_rule_runner.write_files( {"demo/f.txt": "", "demo/BUILD": "generator(sources=['f.txt'])"} ) specs = [ AddressLiteralSpec("demo"), SiblingAddresses("demo"), DescendantAddresses("demo"), AscendantAddresses("demo"), # We also include targets generated from `demo` to ensure that the final result has both # the generator and its generated targets. AddressLiteralSpec("demo", None, "f.txt"), AddressLiteralSpec("demo/f.txt"), ] assert resolve_address_specs(address_specs_rule_runner, specs) == { Address("demo"), Address("demo", generated_name="f.txt"), Address("demo", relative_file_path="f.txt"), }
def test_address_specs_filter_by_tag( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.set_options(["--tag=+integration"]) address_specs_rule_runner.create_file("demo/f.txt") address_specs_rule_runner.add_to_build_file( "demo", dedent("""\ mock_tgt(name="a", sources=["f.txt"]) mock_tgt(name="b", sources=["f.txt"], tags=["integration"]) mock_tgt(name="c", sources=["f.txt"], tags=["ignore"]) """), ) assert resolve_address_specs( address_specs_rule_runner, [SiblingAddresses("demo")]) == {Address("demo", target_name="b")} # The same filtering should work when given literal addresses, including file addresses. # For file addresses, we look up the `tags` field of the original BUILD target. literals_result = resolve_address_specs( address_specs_rule_runner, [ AddressLiteralSpec("demo", "a"), AddressLiteralSpec("demo", "b"), AddressLiteralSpec("demo", "c"), AddressLiteralSpec("demo/f.txt", "a"), AddressLiteralSpec("demo/f.txt", "b"), AddressLiteralSpec("demo/f.txt", "c"), ], ) assert literals_result == { Address("demo", relative_file_path="f.txt", target_name="b"), Address("demo", target_name="b"), }
def test_specs_to_dirs() -> None: assert specs_to_dirs(RawSpecs(description_of_origin="tests")) == ("", ) assert specs_to_dirs( RawSpecs(address_literals=(AddressLiteralSpec("src/python/foo"), ), description_of_origin="tests")) == ("src/python/foo", ) assert specs_to_dirs( RawSpecs(dir_literals=(DirLiteralSpec("src/python/foo"), ), description_of_origin="tests")) == ("src/python/foo", ) assert specs_to_dirs( RawSpecs( address_literals=( AddressLiteralSpec("src/python/foo"), AddressLiteralSpec("src/python/bar"), ), description_of_origin="tests", )) == ("src/python/foo", "src/python/bar") with pytest.raises(ValueError): specs_to_dirs( RawSpecs(file_literals=(FileLiteralSpec("src/python/foo.py"), ), description_of_origin="tests")) with pytest.raises(ValueError): specs_to_dirs( RawSpecs( address_literals=(AddressLiteralSpec("src/python/bar", "tgt"), ), description_of_origin="tests", )) with pytest.raises(ValueError): specs_to_dirs( RawSpecs( address_literals=(AddressLiteralSpec( "src/python/bar", target_component=None, generated_component="gen"), ), description_of_origin="tests", ))
def test_specs_to_dirs() -> None: assert specs_to_dirs(Specs(AddressSpecs([]), FilesystemSpecs([]))) == ("", ) assert specs_to_dirs( Specs(AddressSpecs([AddressLiteralSpec("src/python/foo")]), FilesystemSpecs([]))) == ("src/python/foo", ) assert specs_to_dirs( Specs(AddressSpecs([]), FilesystemSpecs([DirLiteralSpec("src/python/foo") ]))) == ("src/python/foo", ) assert specs_to_dirs( Specs( AddressSpecs([ AddressLiteralSpec("src/python/foo"), AddressLiteralSpec("src/python/bar") ]), FilesystemSpecs([]), )) == ("src/python/foo", "src/python/bar") with pytest.raises(ValueError): specs_to_dirs( Specs(AddressSpecs([]), FilesystemSpecs([FileLiteralSpec("src/python/foo.py")]))) with pytest.raises(ValueError): specs_to_dirs( Specs(AddressSpecs([AddressLiteralSpec("src/python/bar", "tgt")]), FilesystemSpecs([]))) with pytest.raises(ValueError): specs_to_dirs( Specs( AddressSpecs([ AddressLiteralSpec("src/python/bar", target_component=None, generated_component="gen") ]), FilesystemSpecs([]), ))
def single_target_run( self, *, console: MockConsole, program_text: bytes, address_spec: str, ) -> Run: workspace = Workspace(self.scheduler) interactive_runner = InteractiveRunner(self.scheduler) class TestRunFieldSet(RunFieldSet): required_fields = () class TestBinaryTarget(Target): alias = "binary" core_fields = () address = Address.parse(address_spec) target = TestBinaryTarget({}, address=address) target_with_origin = TargetWithOrigin( target, AddressLiteralSpec(address.spec_path, address.target_name)) field_set = TestRunFieldSet.create(target) res = run_rule_with_mocks( run, rule_args=[ create_goal_subsystem(RunSubsystem, args=[]), create_subsystem(GlobalOptions, pants_workdir=self.pants_workdir), console, interactive_runner, workspace, BuildRoot(), ], mock_gets=[ MockGet( product_type=TargetsToValidFieldSets, subject_type=TargetsToValidFieldSetsRequest, mock=lambda _: TargetsToValidFieldSets( {target_with_origin: [field_set]}), ), MockGet( product_type=RunRequest, subject_type=TestRunFieldSet, mock=lambda _: self.create_mock_run_request(program_text), ), ], ) return cast(Run, res)
def parse_spec(self, spec: str) -> AddressSpec | FilesystemSpec: """Parse the given spec into an `AddressSpec` or `FilesystemSpec` object. :raises: CmdLineSpecParser.BadSpecError if the address selector could not be parsed. """ if spec.endswith("::"): spec_path = spec[:-len("::")] return DescendantAddresses( directory=self._normalize_spec_path(spec_path)) if spec.endswith(":"): spec_path = spec[:-len(":")] return SiblingAddresses( directory=self._normalize_spec_path(spec_path)) if ":" in spec or "#" in spec: tgt_parts = spec.split(":", maxsplit=1) path_component = tgt_parts[0] if len(tgt_parts) == 1: target_component = None generated_parts = path_component.split("#", maxsplit=1) if len(generated_parts) == 1: generated_component = None else: path_component, generated_component = generated_parts else: generated_parts = tgt_parts[1].split("#", maxsplit=1) if len(generated_parts) == 1: target_component = generated_parts[0] generated_component = None else: target_component, generated_component = generated_parts return AddressLiteralSpec( path_component=self._normalize_spec_path(path_component), target_component=target_component, generated_component=generated_component, ) if spec.startswith("!"): return FileIgnoreSpec(spec[1:]) if "*" in spec: return FileGlobSpec(spec) if PurePath(spec).suffix: return FileLiteralSpec(self._normalize_spec_path(spec)) spec_path = self._normalize_spec_path(spec) if spec_path == ".": return DirLiteralSpec("") # 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) return DirLiteralSpec(spec_path)
def test_address_specs_more_specific() -> None: literal_addr = AddressLiteralSpec(path_component="foo/bar", target_component="baz") sibling_addresses = SiblingAddresses(directory="foo/bar") ascendant_addresses = AscendantAddresses(directory="foo/bar") descendant_addresses = DescendantAddresses(directory="foo/bar") assert literal_addr == AddressSpecs.more_specific(literal_addr, None) assert literal_addr == AddressSpecs.more_specific(literal_addr, sibling_addresses) assert literal_addr == AddressSpecs.more_specific(literal_addr, ascendant_addresses) assert literal_addr == AddressSpecs.more_specific(literal_addr, descendant_addresses) assert literal_addr == AddressSpecs.more_specific(None, literal_addr) assert literal_addr == AddressSpecs.more_specific(sibling_addresses, literal_addr) assert literal_addr == AddressSpecs.more_specific(ascendant_addresses, literal_addr) assert literal_addr == AddressSpecs.more_specific(descendant_addresses, literal_addr) assert sibling_addresses == AddressSpecs.more_specific( sibling_addresses, None) assert sibling_addresses == AddressSpecs.more_specific( sibling_addresses, ascendant_addresses) assert sibling_addresses == AddressSpecs.more_specific( sibling_addresses, descendant_addresses) assert sibling_addresses == AddressSpecs.more_specific( None, sibling_addresses) assert sibling_addresses == AddressSpecs.more_specific( ascendant_addresses, sibling_addresses) assert sibling_addresses == AddressSpecs.more_specific( descendant_addresses, sibling_addresses) assert ascendant_addresses == AddressSpecs.more_specific( ascendant_addresses, None) assert ascendant_addresses == AddressSpecs.more_specific( ascendant_addresses, descendant_addresses) assert ascendant_addresses == AddressSpecs.more_specific( None, ascendant_addresses) assert ascendant_addresses == AddressSpecs.more_specific( descendant_addresses, ascendant_addresses) assert descendant_addresses == AddressSpecs.more_specific( descendant_addresses, None) assert descendant_addresses == AddressSpecs.more_specific( None, descendant_addresses)
def test_address_specs_file_does_not_belong_to_target(self) -> None: """Even if a file's address file exists and target exist, we should validate that the file actually belongs to that target.""" self.create_file("demo/f.txt") self.add_to_build_file( "demo", dedent("""\ mock_tgt(name='owner', sources=['f.txt']) mock_tgt(name='not_owner') """), ) with pytest.raises(ExecutionError) as exc: self.resolve_address_specs( [AddressLiteralSpec("demo/f.txt", "not_owner")]) assert "does not match a file demo/f.txt" in str(exc.value)
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_address_specs_filter_by_tag( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.set_options(["--tag=+integration"]) address_specs_rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ generator(name="a", sources=["f.txt"]) generator(name="b", sources=["f.txt"], tags=["integration"]) generator(name="c", sources=["f.txt"], tags=["ignore"]) """), }) assert resolve_address_specs(address_specs_rule_runner, [SiblingAddresses("demo")]) == { Address("demo", target_name="b"), Address("demo", target_name="b", relative_file_path="f.txt"), Address("demo", target_name="b", generated_name="f.txt"), } # The same filtering should work when given literal addresses, including generated targets and # file addresses. literals_result = resolve_address_specs( address_specs_rule_runner, [ AddressLiteralSpec("demo", "a"), AddressLiteralSpec("demo", "b"), AddressLiteralSpec("demo", "c"), AddressLiteralSpec("demo/f.txt", "a"), AddressLiteralSpec("demo/f.txt", "b"), AddressLiteralSpec("demo", "a", "f.txt"), AddressLiteralSpec("demo", "b", "f.txt"), AddressLiteralSpec("demo", "c", "f.txt"), ], ) assert literals_result == { Address("demo", target_name="b"), Address("demo", target_name="b", generated_name="f.txt"), Address("demo", target_name="b", relative_file_path="f.txt"), }
def test_address_specs_generated_target_does_not_belong_to_generator( address_specs_rule_runner: RuleRunner, ) -> None: address_specs_rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ generator(name='owner', sources=['f.txt']) generator(name='not_owner') """), }) with pytest.raises(ExecutionError) as exc: resolve_address_specs(address_specs_rule_runner, [AddressLiteralSpec("demo/f.txt", "not_owner")]) assert ( f"The address `demo/f.txt:not_owner` is not generated by the `{MockTargetGenerator.alias}` " f"target `demo:not_owner`") in str(exc.value)
def test_address_specs_filter_by_tag(self) -> None: self.create_file("demo/f.txt") self.add_to_build_file( "demo", dedent("""\ mock_tgt(name="a", sources=["f.txt"]) mock_tgt(name="b", sources=["f.txt"], tags=["integration"]) mock_tgt(name="c", sources=["f.txt"], tags=["ignore"]) """), ) bootstrapper = create_options_bootstrapper(args=["--tag=+integration"]) assert self.resolve_address_specs([SiblingAddresses("demo")], bootstrapper=bootstrapper) == { AddressWithOrigin( Address("demo", target_name="b"), SiblingAddresses("demo")) } # The same filtering should work when given literal addresses, including file addresses. # For file addresses, we look up the `tags` field of the original base target. literals_result = self.resolve_address_specs( [ AddressLiteralSpec("demo", "a"), AddressLiteralSpec("demo", "b"), AddressLiteralSpec("demo", "c"), AddressLiteralSpec("demo/f.txt", "a"), AddressLiteralSpec("demo/f.txt", "b"), AddressLiteralSpec("demo/f.txt", "c"), ], bootstrapper=bootstrapper, ) assert literals_result == { AddressWithOrigin( Address("demo", relative_file_path="f.txt", target_name="b"), AddressLiteralSpec("demo/f.txt", "b"), ), AddressWithOrigin(Address("demo", target_name="b"), AddressLiteralSpec("demo", "b")), }
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_generated_target_does_not_belong_to_generator( rule_runner: RuleRunner, ) -> None: rule_runner.write_files({ "demo/f.txt": "", "demo/other.txt": "", "demo/BUILD": dedent("""\ file_generator(name='owner', sources=['f.txt']) file_generator(name='not_owner', sources=['other.txt']) """), }) with pytest.raises(ExecutionError) as exc: resolve_raw_specs_without_file_owners( rule_runner, [AddressLiteralSpec("demo/f.txt", "not_owner")]) assert ( "The address `demo/f.txt:not_owner` was not generated by the target `demo:not_owner`" ) in str(exc.value)
def test_address_specs_filter_by_exclude_pattern( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.set_options( ["--exclude-target-regexp=exclude_me.*"]) address_specs_rule_runner.write_files({ "demo/f.txt": "", "demo/BUILD": dedent("""\ generator(name="exclude_me", sources=["f.txt"]) generator(name="not_me", sources=["f.txt"]) """), }) assert resolve_address_specs(address_specs_rule_runner, [SiblingAddresses("demo")]) == { Address("demo", target_name="not_me"), Address("demo", target_name="not_me", relative_file_path="f.txt"), Address("demo", target_name="not_me", generated_name="f.txt"), } # The same filtering should work when given literal addresses, including generated targets and # file addresses. literals_result = resolve_address_specs( address_specs_rule_runner, [ AddressLiteralSpec("demo", "exclude_me"), AddressLiteralSpec("demo", "not_me"), AddressLiteralSpec("demo", "exclude_me", "f.txt"), AddressLiteralSpec("demo", "not_me", "f.txt"), AddressLiteralSpec("demo/f.txt", "exclude_me"), AddressLiteralSpec("demo/f.txt", "not_me"), ], ) assert literals_result == { Address("demo", target_name="not_me"), Address("demo", target_name="not_me", relative_file_path="f.txt"), Address("demo", target_name="not_me", generated_name="f.txt"), }
def test_address_specs_filter_by_exclude_pattern(self) -> None: self.create_file("demo/f.txt") self.add_to_build_file( "demo", dedent("""\ mock_tgt(name="exclude_me", sources=["f.txt"]) mock_tgt(name="not_me", sources=["f.txt"]) """), ) bootstrapper = create_options_bootstrapper( args=["--exclude-target-regexp=exclude_me.*"]) assert self.resolve_address_specs( [SiblingAddresses("demo")], bootstrapper=bootstrapper) == { AddressWithOrigin(Address("demo", target_name="not_me"), SiblingAddresses("demo")) } # The same filtering should work when given literal addresses, including file addresses. # The filtering will operate against the normalized Address.spec. literals_result = self.resolve_address_specs( [ AddressLiteralSpec("demo", "exclude_me"), AddressLiteralSpec("demo", "not_me"), AddressLiteralSpec("demo/f.txt", "exclude_me"), AddressLiteralSpec("demo/f.txt", "not_me"), ], bootstrapper=bootstrapper, ) assert literals_result == { AddressWithOrigin( Address("demo", relative_file_path="f.txt", target_name="not_me"), AddressLiteralSpec("demo/f.txt", "not_me"), ), AddressWithOrigin(Address("demo", target_name="not_me"), AddressLiteralSpec("demo", "not_me")), }
def address_literal(directory: str, name: str | None = None) -> AddressLiteralSpec: name = name if name is not None else os.path.basename(directory) return AddressLiteralSpec(directory, name)