Esempio n. 1
0
def filter(coordinate, lockfile, transitive) -> Sequence[Coordinate]:
    key = CoursierResolveKey("example", "example.json", EMPTY_DIGEST)
    if transitive:
        root, deps = lockfile.dependencies(key, coordinate)
    else:
        root, deps = lockfile.direct_dependencies(key, coordinate)
    return list(i.coord for i in (root, *deps))
Esempio n. 2
0
def make_resolve(
    rule_runner: RuleRunner,
    resolve_name: str = "jvm-default",
    resolve_path: str = "3rdparty/jvm/default.lock",
) -> CoursierResolveKey:
    digest = rule_runner.request(Digest, [PathGlobs([resolve_path])])
    return CoursierResolveKey(name=resolve_name,
                              path=resolve_path,
                              digest=digest)
Esempio n. 3
0
def make_resolve(
    rule_runner: RuleRunner,
    resolve_name: str = "test",
    resolve_path: str = "coursier_resolve.lockfile",
) -> CoursierResolveKey:
    digest = rule_runner.request(Digest, [PathGlobs([resolve_path])])
    return CoursierResolveKey(name=resolve_name,
                              path=resolve_path,
                              digest=digest)
Esempio n. 4
0
async def materialize_classpath_for_tool(
        request: ToolClasspathRequest) -> ToolClasspath:
    if request.artifact_requirements:
        resolution = await Get(CoursierResolvedLockfile, ArtifactRequirements,
                               request.artifact_requirements)
    else:
        lockfile_req = request.lockfile
        assert lockfile_req is not None
        regen_command = f"`{GenerateLockfilesSubsystem.name} --resolve={lockfile_req.resolve_name}`"
        if lockfile_req.read_lockfile_dest == DEFAULT_TOOL_LOCKFILE:
            lockfile_bytes = importlib.resources.read_binary(
                *lockfile_req.default_lockfile_resource)
            resolution = CoursierResolvedLockfile.from_serialized(
                lockfile_bytes)
        else:
            lockfile_snapshot = await Get(
                Snapshot, PathGlobs([lockfile_req.read_lockfile_dest]))
            if not lockfile_snapshot.files:
                raise ValueError(
                    f"No lockfile found at {lockfile_req.read_lockfile_dest}, which is configured "
                    f"by the option {lockfile_req.lockfile_option_name}."
                    f"Run {regen_command} to generate it.")

            resolution = await Get(
                CoursierResolvedLockfile,
                CoursierResolveKey(
                    name=lockfile_req.resolve_name,
                    path=lockfile_req.read_lockfile_dest,
                    digest=lockfile_snapshot.digest,
                ),
            )

        # Validate that the lockfile is correct.
        lockfile_inputs = await Get(
            ArtifactRequirements,
            GatherJvmCoordinatesRequest(lockfile_req.artifact_inputs,
                                        lockfile_req.artifact_option_name),
        )
        if resolution.metadata and not resolution.metadata.is_valid_for(
                lockfile_inputs, LockfileContext.TOOL):
            raise ValueError(
                f"The lockfile {lockfile_req.read_lockfile_dest} (configured by the option "
                f"{lockfile_req.lockfile_option_name}) was generated with different requirements "
                f"than are currently set via {lockfile_req.artifact_option_name}. Run "
                f"{regen_command} to regenerate the lockfile.")

    classpath_entries = await Get(ResolvedClasspathEntries,
                                  CoursierResolvedLockfile, resolution)
    merged_snapshot = await Get(
        Snapshot,
        MergeDigests(classpath_entry.digest
                     for classpath_entry in classpath_entries))
    if request.prefix is not None:
        merged_snapshot = await Get(
            Snapshot, AddPrefix(merged_snapshot.digest, request.prefix))
    return ToolClasspath(merged_snapshot)
Esempio n. 5
0
    def classify(
        targets: Sequence[Target],
        members: Sequence[type[ClasspathEntryRequest]],
        generators: FrozenDict[type[ClasspathEntryRequest], frozenset[type[SourcesField]]],
    ) -> tuple[type[ClasspathEntryRequest], type[ClasspathEntryRequest] | None]:

        factory = ClasspathEntryRequestFactory(tuple(members), generators)

        req = factory.for_targets(
            CoarsenedTarget(targets, ()),
            CoursierResolveKey("example", "path", EMPTY_DIGEST),
        )
        return (type(req), type(req.prerequisite) if req.prerequisite else None)
Esempio n. 6
0
async def select_coursier_resolve_for_targets(
        coarsened_targets: CoarsenedTargets,
        jvm: JvmSubsystem) -> CoursierResolveKey:
    """Selects and validates (transitively) a single resolve for a set of roots in a compile graph.

    In most cases, a `CoursierResolveKey` should be requested for a single `CoarsenedTarget` root,
    which avoids coupling un-related roots unnecessarily. But in other cases, a single compatible
    resolve is required for multiple roots (such as when running a `repl` over unrelated code), and
    in that case there might be multiple CoarsenedTargets.
    """
    targets = list(coarsened_targets.closure())

    # Find a single resolve that is compatible with all targets in the closure.
    compatible_resolve: str | None = None
    all_compatible = True
    for tgt in targets:
        if not tgt.has_field(JvmResolveField):
            continue
        resolve = tgt[JvmResolveField].normalized_value(jvm)
        if compatible_resolve is None:
            compatible_resolve = resolve
        elif resolve != compatible_resolve:
            all_compatible = False

    if not all_compatible:
        raise NoCompatibleResolve(
            jvm, "The selected targets did not have a resolve in common",
            targets)
    resolve = compatible_resolve or jvm.default_resolve

    # Load the resolve.
    resolve_path = jvm.resolves[resolve]
    lockfile_source = PathGlobs(
        [resolve_path],
        glob_match_error_behavior=GlobMatchErrorBehavior.error,
        description_of_origin=f"The resolve `{resolve}` from `[jvm].resolves`",
    )
    resolve_digest = await Get(Digest, PathGlobs, lockfile_source)
    return CoursierResolveKey(resolve, resolve_path, resolve_digest)
Esempio n. 7
0
async def select_coursier_resolve_for_targets(
    targets: Targets,
    jvm: JvmSubsystem,
    coursier: Coursier,
) -> CoursierResolveKey:
    """Determine the lockfile that applies for given JVM targets and resolve configuration.

    This walks the target's transitive dependencies to find the set of resolve names that are
    compatible with the entirety of this build (defined as the intersection of all
    `compatible_resolves` fields). This is then compared with the JVM subsystem's `resolves` flag to
    determine which resolve to use for this scenario.
    """

    default_resolve_name: str | None = jvm.options.default_resolve

    transitive_targets = await Get(
        TransitiveTargets,
        TransitiveTargetsRequest(target.address for target in targets))

    transitive_jvm_resolve_names = [
        target[JvmCompatibleResolveNamesField].value
        for target in transitive_targets.closure
        if target.has_field(JvmCompatibleResolveNamesField)
    ]

    any_unspecified_resolves = any(i is None
                                   for i in transitive_jvm_resolve_names)

    if not default_resolve_name and any_unspecified_resolves:
        raise CoursierError(
            "Either the `--jvm-default-resolve` must be set, or all JVM source targets must "
            "specify their `compatible_resolves` in order to materialize the classpath."
        )

    transitive_jvm_resolve_names_ = (
        # If any resolves are unspecified, the only acceptable resolve will be the default one,
        # but individual targets must also support the default resolve if they specify _any_
        # compatible resolves.
        set(resolves) if resolves is not None else
        {default_resolve_name} if default_resolve_name is not None else set()
        for resolves in transitive_jvm_resolve_names)
    compatible_resolves = reduce(operator.iand, transitive_jvm_resolve_names_)

    if not compatible_resolves:
        raise CoursierError(
            "There are no resolve names that are compatible with all of the targets in this build. "
            f"The targets are {targets}.  At least one resolve must be compatible with every "
            "target -- including dependencies and transitive dependencies in order to complete the "
            "build.")

    available_resolves = jvm.options.resolves
    if not available_resolves:
        raise CoursierError(
            "No values were set for `--jvm-resolves`, so we can't fulfil any dependencies for the "
            "build. You can fix this by specifying `--jvm-resolves`.")
    available_resolve_names = set(available_resolves.keys())
    usable_resolve_names = compatible_resolves & available_resolve_names

    if not usable_resolve_names:
        raise CoursierError(
            "None of the resolves specified in the `--jvm-resolves` option are compatible with "
            "all of the targets (including dependencies) in this build. You supplied the following "
            f"resolve names: {available_resolve_names}, but the compatible resolve names are: "
            f"{compatible_resolves}.")

    # Resolve to use is:
    # - The resolve name that is specifically requested (by `--jvm-use-resolve`; eventually by
    #     the `deploy_jar` target, but not yet)
    # - The default resolve name

    # See if any target has a specified resolve:

    specified_resolve: str | None = jvm.options.use_resolve
    if specified_resolve is not None and specified_resolve not in available_resolve_names:
        raise CoursierError(
            f"You specified the resolve name `{specified_resolve}`, however, that resolve does not "
            f"exist. The available resolve names are `{available_resolve_names}`."
        )

    resolve_name: str
    if specified_resolve:
        resolve_name = specified_resolve
    if default_resolve_name is not None and default_resolve_name in usable_resolve_names:
        resolve_name = default_resolve_name
    else:
        # Pick a consistent default resolve name
        resolve_name = min(usable_resolve_names)

    resolve_path = available_resolves[resolve_name]

    lockfile_source = PathGlobs(
        [resolve_path],
        glob_match_error_behavior=GlobMatchErrorBehavior.error,
        description_of_origin=
        f"Path associated with the JVM resolve with name '{resolve_name}'",
    )
    resolve_digest = await Get(Digest, PathGlobs, lockfile_source)

    return CoursierResolveKey(resolve_name, resolve_path, resolve_digest)