async def resolve_scala_library_for_resolve( request: ScalaRuntimeForResolveRequest, jvm_artifact_targets: AllJvmArtifactTargets, jvm: JvmSubsystem, scala_subsystem: ScalaSubsystem, ) -> ScalaRuntimeForResolve: scala_version = scala_subsystem.version_for_resolve(request.resolve_name) for tgt in jvm_artifact_targets: if tgt[JvmResolveField].normalized_value(jvm) != request.resolve_name: continue artifact = ArtifactRequirement.from_jvm_artifact_target(tgt) if ( artifact.coordinate.group != SCALA_LIBRARY_GROUP or artifact.coordinate.artifact != SCALA_LIBRARY_ARTIFACT ): continue if artifact.coordinate.version != scala_version: raise ConflictingScalaLibraryVersionInResolveError( request.resolve_name, scala_version, artifact.coordinate ) return ScalaRuntimeForResolve(tgt.address) raise MissingScalaLibraryInResolveError(request.resolve_name, scala_version)
async def setup_user_lockfile_requests( requested: RequestedJVMUserResolveNames, all_targets: AllTargets, jvm_subsystem: JvmSubsystem, ) -> UserGenerateLockfiles: resolve_to_artifacts: Mapping[ str, OrderedSet[ArtifactRequirement]] = defaultdict(OrderedSet) for tgt in sorted(all_targets, key=lambda t: t.address): if not tgt.has_field(JvmArtifactResolveField): continue artifact = ArtifactRequirement.from_jvm_artifact_target(tgt) resolve = tgt[JvmResolveField].normalized_value(jvm_subsystem) resolve_to_artifacts[resolve].add(artifact) # Generate a JVM lockfile request for each requested resolve. This step also allows other backends to # validate the proposed set of artifact requirements for each resolve. jvm_lockfile_requests = await MultiGet( Get( GenerateJvmLockfile, _ValidateJvmArtifactsRequest( artifacts=ArtifactRequirements( resolve_to_artifacts.get(resolve, ())), resolve_name=resolve, ), ) for resolve in requested) return UserGenerateLockfiles(jvm_lockfile_requests)
async def gather_coordinates_for_jvm_lockfile( request: GatherJvmCoordinatesRequest, ) -> ArtifactRequirements: # Separate `artifact_inputs` by whether the strings parse as an `Address` or not. requirements: set[ArtifactRequirement] = set() candidate_address_inputs: set[AddressInput] = set() bad_artifact_inputs = [] for artifact_input in request.artifact_inputs: # Try parsing as a `Coordinate` first since otherwise `AddressInput.parse` will try to see if the # group name is a file on disk. if 2 <= artifact_input.count(":") <= 3: try: maybe_coord = Coordinate.from_coord_str( artifact_input).as_requirement() requirements.add(maybe_coord) continue except Exception: pass try: address_input = AddressInput.parse( artifact_input, description_of_origin=f"the option `{request.option_name}`") candidate_address_inputs.add(address_input) except Exception: bad_artifact_inputs.append(artifact_input) if bad_artifact_inputs: raise ValueError( "The following values could not be parsed as an address nor as a JVM coordinate string. " f"The problematic inputs supplied to the `{request.option_name}` option were: " f"{', '.join(bad_artifact_inputs)}.") # Gather coordinates from the provided addresses. addresses = await MultiGet( Get(Address, AddressInput, ai) for ai in candidate_address_inputs) all_supplied_targets = await Get(Targets, Addresses(addresses)) other_targets = [] for tgt in all_supplied_targets: if JvmArtifactFieldSet.is_applicable(tgt): requirements.add(ArtifactRequirement.from_jvm_artifact_target(tgt)) else: other_targets.append(tgt) if other_targets: raise ValueError( softwrap(f""" The following addresses reference targets that are not `jvm_artifact` targets. Please only supply the addresses of `jvm_artifact` for the `{request.option_name}` option. The problematic addresses are: {', '.join(str(tgt.address) for tgt in other_targets)}. """)) return ArtifactRequirements(requirements)
async def collect_thirdparty_modules( request: ThirdpartyModulesRequest, classpath_entry_request: ClasspathEntryRequestFactory, ) -> ThirdpartyModules: coarsened_targets = await Get(CoarsenedTargets, Addresses, request.addresses) resolve = await Get(CoursierResolveKey, CoarsenedTargets, coarsened_targets) lockfile = await Get(CoursierResolvedLockfile, CoursierResolveKey, resolve) applicable_lockfile_entries: dict[CoursierLockfileEntry, CoarsenedTarget] = {} for ct in coarsened_targets.coarsened_closure(): for tgt in ct.members: if not JvmArtifactFieldSet.is_applicable(tgt): continue artifact_requirement = ArtifactRequirement.from_jvm_artifact_target( tgt) entry = get_entry_for_coord(lockfile, artifact_requirement.coordinate) if not entry: _logger.warning( f"No lockfile entry for {artifact_requirement.coordinate} in resolve {resolve.name}." ) continue applicable_lockfile_entries[entry] = ct classpath_entries = await MultiGet( Get( ClasspathEntry, ClasspathEntryRequest, classpath_entry_request.for_targets(component=target, resolve=resolve), ) for target in applicable_lockfile_entries.values()) resolve_digest = await Get( Digest, MergeDigests(cpe.digest for cpe in classpath_entries)) return ThirdpartyModules( resolve, dict(zip(applicable_lockfile_entries, classpath_entries)), resolve_digest, )
def find_jvm_artifacts_or_raise( required_coordinates: Iterable[Coordinate | UnversionedCoordinate], resolve: str, jvm_artifact_targets: AllJvmArtifactTargets, jvm: JvmSubsystem, ) -> frozenset[Address]: remaining_coordinates: set[Coordinate | UnversionedCoordinate] = set( required_coordinates) addresses: set[Address] = set() for tgt in jvm_artifact_targets: if tgt[JvmResolveField].normalized_value(jvm) != resolve: continue artifact = ArtifactRequirement.from_jvm_artifact_target(tgt) found_coordinates: set[Coordinate | UnversionedCoordinate] = set() for coordinate in remaining_coordinates: if isinstance(coordinate, Coordinate): if (artifact.coordinate.group != coordinate.group or artifact.coordinate.artifact != coordinate.artifact): continue if artifact.coordinate.version != coordinate.version: raise ConflictingJvmArtifactVersion( group=coordinate.group, artifact=coordinate.artifact, required_version=coordinate.version, found_coordinate=artifact.coordinate, ) elif isinstance(coordinate, UnversionedCoordinate): if (artifact.coordinate.group != coordinate.group or artifact.coordinate.artifact != coordinate.artifact): continue found_coordinates.add(coordinate) if found_coordinates: remaining_coordinates.difference_update(found_coordinates) addresses.add(tgt.address) if remaining_coordinates: raise MissingJvmArtifacts(remaining_coordinates) return frozenset(addresses)
async def fetch_with_coursier( request: CoursierFetchRequest) -> FallibleClasspathEntry: # TODO: Loading this per JvmArtifact. lockfile = await Get(CoursierResolvedLockfile, CoursierResolveKey, request.resolve) requirement = ArtifactRequirement.from_jvm_artifact_target( request.component.representative) if lockfile.metadata and not lockfile.metadata.is_valid_for( [requirement], LockfileContext.USER): raise ValueError( f"Requirement `{requirement.to_coord_arg_str()}` has changed since the lockfile " f"for {request.resolve.path} was generated. Run `{bin_name()} generate-lockfiles` to update your " "lockfile based on the new requirements.") # All of the transitive dependencies are exported. # TODO: Expose an option to control whether this exports only the root, direct dependencies, # transitive dependencies, etc. assert len(request.component.members ) == 1, "JvmArtifact does not have dependencies." root_entry, transitive_entries = lockfile.dependencies( request.resolve, requirement.coordinate, ) classpath_entries = await MultiGet( Get(ClasspathEntry, CoursierLockfileEntry, entry) for entry in (root_entry, *transitive_entries)) exported_digest = await Get( Digest, MergeDigests(cpe.digest for cpe in classpath_entries)) return FallibleClasspathEntry( description=str(request.component), result=CompileResult.SUCCEEDED, output=ClasspathEntry.merge(exported_digest, classpath_entries), exit_code=0, )
async def scala_bsp_dependency_modules( request: ScalaBSPDependencyModulesRequest, build_root: BuildRoot, ) -> BSPDependencyModulesResult: coarsened_targets = await Get( CoarsenedTargets, Addresses([fs.address for fs in request.field_sets])) resolve = await Get(CoursierResolveKey, CoarsenedTargets, coarsened_targets) lockfile = await Get(CoursierResolvedLockfile, CoursierResolveKey, resolve) # TODO: Can this use ClasspathEntryRequest? transitive_targets = await Get( TransitiveTargets, TransitiveTargetsRequest(roots=[ tgt.address for coarsened_target in coarsened_targets for tgt in coarsened_target.members ]), ) artifact_requirements = [ ArtifactRequirement.from_jvm_artifact_target(tgt) for tgt in transitive_targets.closure if JvmArtifactFieldSet.is_applicable(tgt) ] applicable_lockfile_entries: set[CoursierLockfileEntry] = set() for artifact_requirement in artifact_requirements: entry = get_entry_for_coord(lockfile, artifact_requirement.coordinate) if not entry: _logger.warning( f"No lockfile entry for {artifact_requirement.coordinate} in resolve {resolve.name}." ) continue applicable_lockfile_entries.add(entry) resolve_digest = await Get( Digest, CreateDigest([ FileEntry(entry.file_name, entry.file_digest) for entry in applicable_lockfile_entries ]), ) resolve_digest = await Get( Digest, AddPrefix(resolve_digest, f"jvm/resolves/{resolve.name}/lib")) modules = [ DependencyModule( name=f"{entry.coord.group}:{entry.coord.artifact}", version=entry.coord.version, data=MavenDependencyModule( organization=entry.coord.group, name=entry.coord.artifact, version=entry.coord.version, scope=None, artifacts=(MavenDependencyModuleArtifact( uri=build_root.pathlib_path.joinpath( f".pants.d/bsp/jvm/resolves/{resolve.name}/lib/{entry.file_name}" ).as_uri()), ), ), ) for entry in applicable_lockfile_entries ] return BSPDependencyModulesResult( modules=tuple(modules), digest=resolve_digest, )