예제 #1
0
    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)
예제 #2
0
def test_raw_specs_with_only_file_owners_glob(rule_runner: RuleRunner) -> None:
    rule_runner.write_files({
        "demo/f1.txt":
        "",
        "demo/f2.txt":
        "",
        "demo/BUILD":
        dedent("""\
                file_generator(name='generator', sources=['*.txt'])
                nonfile_generator(name='nonfile')
                target(name='not-generator', sources=['*.txt'])
                target(name='skip-me', sources=['*.txt'])
                target(name='bad-tag', sources=['*.txt'], tags=['skip'])
                """),
    })
    rule_runner.set_options(["--tag=-skip", "--exclude-target-regexp=skip-me"])
    all_unskipped_addresses = [
        Address("demo", target_name="not-generator"),
        Address("demo", target_name="generator", relative_file_path="f1.txt"),
        Address("demo", target_name="generator", relative_file_path="f2.txt"),
    ]

    assert (resolve_raw_specs_with_only_file_owners(
        rule_runner, [FileGlobSpec("demo/*.txt")]) == all_unskipped_addresses)
    # We should deduplicate between glob and literal specs.
    assert (resolve_raw_specs_with_only_file_owners(
        rule_runner,
        [FileGlobSpec("demo/*.txt"),
         FileLiteralSpec("demo/f1.txt")],
    ) == all_unskipped_addresses)
예제 #3
0
    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
예제 #4
0
def test_raw_specs_with_only_file_owners_nonexistent_file(
        rule_runner: RuleRunner) -> None:
    spec = FileLiteralSpec("demo/fake.txt")
    with engine_error(contains='Unmatched glob from tests: "demo/fake.txt"'):
        resolve_raw_specs_with_only_file_owners(rule_runner, [spec])

    assert not resolve_raw_specs_with_only_file_owners(rule_runner, [spec],
                                                       ignore_nonexistent=True)
예제 #5
0
def test_raw_specs_with_only_file_owners_no_owner(
        rule_runner: RuleRunner) -> None:
    rule_runner.set_options(["--owners-not-found-behavior=error"])
    rule_runner.write_files({"no_owners/f.txt": ""})
    # Error for literal specs.
    with pytest.raises(ExecutionError) as exc:
        resolve_raw_specs_with_only_file_owners(
            rule_runner, [FileLiteralSpec("no_owners/f.txt")])
    assert "No owning targets could be found for the file `no_owners/f.txt`" in str(
        exc.value)

    # Do not error for glob specs.
    assert not resolve_raw_specs_with_only_file_owners(
        rule_runner, [FileGlobSpec("no_owners/*.txt")])
예제 #6
0
def test_raw_specs_with_only_file_owners_literal_file(
        rule_runner: RuleRunner) -> None:
    rule_runner.write_files({
        "demo/f1.txt":
        "",
        "demo/f2.txt":
        "",
        "demo/BUILD":
        dedent("""\
                file_generator(name='generator', sources=['*.txt'])
                nonfile_generator(name='nonfile')
                target(name='not-generator', sources=['*.txt'])
                """),
    })
    assert resolve_raw_specs_with_only_file_owners(
        rule_runner, [FileLiteralSpec("demo/f1.txt")]) == [
            Address("demo", target_name="not-generator"),
            Address("demo",
                    target_name="generator",
                    relative_file_path="f1.txt"),
        ]
예제 #7
0
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",
            ))
예제 #8
0
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([]),
            ))
예제 #9
0
def calculate_specs(
    options_bootstrapper: OptionsBootstrapper,
    options: Options,
    session: SchedulerSession,
) -> Specs:
    """Determine the specs for a given Pants run."""
    global_options = options.for_global_scope()
    unmatched_cli_globs = global_options.unmatched_cli_globs.to_glob_match_error_behavior(
    )
    convert_dir_literal_to_address_literal = (
        global_options.use_deprecated_directory_cli_args_semantics)
    if global_options.is_default(
            "use_deprecated_directory_cli_args_semantics"):
        warn_or_error(
            "2.14.0.dev0",
            "`use_deprecated_directory_cli_args_semantics` defaulting to True",
            softwrap(f"""
                Currently, a directory argument like `{bin_name()} test dir` is shorthand for the
                target `dir:dir`, i.e. the target that leaves off `name=`.

                In Pants 2.14, by default, a directory argument will instead match all
                targets/files in the directory.

                To opt into the new and more intuitive semantics early, set
                `use_deprecated_directory_cli_args_semantics = false` in the `[GLOBAL]` section in
                `pants.toml`. Otherwise, set to `true` to silence this warning.
                """),
        )
    specs = SpecsParser().parse_specs(
        options.specs,
        description_of_origin="CLI arguments",
        unmatched_glob_behavior=unmatched_cli_globs,
        convert_dir_literal_to_address_literal=
        convert_dir_literal_to_address_literal,
    )

    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 and changed_options.provided:
        changed_name = "--changed-since" if changed_options.since else "--changed-diffspec"
        specs_description = specs.arguments_provided_description()
        assert specs_description is not None
        raise InvalidSpecConstraint(
            f"You used `{changed_name}` at the same time as using {specs_description}. You can "
            f"only use `{changed_name}` or use normal arguments.")

    if not changed_options.provided:
        return specs

    (git_binary, ) = session.product_request(GitBinary,
                                             [Params(GitBinaryRequest())])
    (maybe_git_worktree, ) = session.product_request(
        MaybeGitWorktree, [Params(GitWorktreeRequest(), git_binary)])
    if not maybe_git_worktree.git_worktree:
        raise InvalidSpecConstraint(
            "The `--changed-*` options are only available if Git is used for the repository."
        )

    changed_files = tuple(
        changed_options.changed_files(maybe_git_worktree.git_worktree))
    file_literal_specs = tuple(FileLiteralSpec(f) for f in changed_files)

    changed_request = ChangedRequest(changed_files, changed_options.dependees)
    (changed_addresses, ) = session.product_request(
        ChangedAddresses, [Params(changed_request, options_bootstrapper)])
    logger.debug("changed addresses: %s", changed_addresses)

    address_literal_specs = []
    for address in cast(ChangedAddresses, changed_addresses):
        address_input = AddressInput.parse(
            address.spec, description_of_origin="`--changed-since`")
        address_literal_specs.append(
            AddressLiteralSpec(
                path_component=address_input.path_component,
                target_component=address_input.target_component,
                generated_component=address_input.generated_component,
                parameters=address_input.parameters,
            ))

    return Specs(
        includes=RawSpecs(
            # We need both address_literals and file_literals to cover all our edge cases, including
            # target-aware vs. target-less goals, e.g. `list` vs `count-loc`.
            address_literals=tuple(address_literal_specs),
            file_literals=file_literal_specs,
            unmatched_glob_behavior=unmatched_cli_globs,
            filter_by_global_options=True,
            from_change_detection=True,
            description_of_origin="`--changed-since`",
        ),
        ignores=RawSpecs(description_of_origin="`--changed-since`"),
    )
예제 #10
0
def test_no_applicable_targets_exception() -> None:
    # Check that we correctly render the error message.
    class Tgt1(Target):
        alias = "tgt1"
        core_fields = ()

    class Tgt2(Target):
        alias = "tgt2"
        core_fields = (MultipleSourcesField, )

    class Tgt3(Target):
        alias = "tgt3"
        core_fields = ()

    # No targets/files specified. Because none of the relevant targets have a sources field, we do
    # not give the filedeps command.
    exc = NoApplicableTargetsException(
        [],
        Specs.empty(),
        UnionMembership({}),
        applicable_target_types=[Tgt1],
        goal_description="the `foo` goal",
    )
    remedy = (
        "Please specify relevant file and/or target arguments. Run `./pants "
        "--filter-target-type=tgt1 list ::` to find all applicable targets in your project."
    )
    assert (dedent(f"""\
            No files or targets specified. The `foo` goal works with these target types:

              * tgt1

            {remedy}""") in str(exc))

    invalid_tgt = Tgt3({}, Address("blah"))
    exc = NoApplicableTargetsException(
        [invalid_tgt],
        Specs(
            includes=RawSpecs(file_literals=(FileLiteralSpec("foo.ext"), ),
                              description_of_origin="tests"),
            ignores=RawSpecs(description_of_origin="tests"),
        ),
        UnionMembership({}),
        applicable_target_types=[Tgt1, Tgt2],
        goal_description="the `foo` goal",
    )
    remedy = (
        "Please specify relevant file and/or target arguments. Run `./pants "
        "--filter-target-type=tgt1,tgt2 list ::` to find all applicable targets in your project, "
        "or run `./pants --filter-target-type=tgt1,tgt2 filedeps ::` to find all "
        "applicable files.")
    assert (dedent(f"""\
            No applicable files or targets matched. The `foo` goal works with these target types:

              * tgt1
              * tgt2

            However, you only specified file arguments with these target types:

              * tgt3

            {remedy}""") in str(exc))

    # Test handling of `Specs`.
    exc = NoApplicableTargetsException(
        [invalid_tgt],
        Specs(
            includes=RawSpecs(address_literals=(AddressLiteralSpec(
                "foo", "bar"), ),
                              description_of_origin="tests"),
            ignores=RawSpecs(description_of_origin="tests"),
        ),
        UnionMembership({}),
        applicable_target_types=[Tgt1],
        goal_description="the `foo` goal",
    )
    assert "However, you only specified target arguments with these target types:" in str(
        exc)
    exc = NoApplicableTargetsException(
        [invalid_tgt],
        Specs(
            includes=RawSpecs(
                address_literals=(AddressLiteralSpec("foo", "bar"), ),
                file_literals=(FileLiteralSpec("foo.ext"), ),
                description_of_origin="tests",
            ),
            ignores=RawSpecs(description_of_origin="tests"),
        ),
        UnionMembership({}),
        applicable_target_types=[Tgt1],
        goal_description="the `foo` goal",
    )
    assert "However, you only specified target and file arguments with these target types:" in str(
        exc)
예제 #11
0
def file_literal(file: str) -> FileLiteralSpec:
    return FileLiteralSpec(file)