blob: dab03974bafc4bcd1917b3b0958281d6092a9812 [file] [log] [blame]
// Copyright 2023 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.google.devtools.build.lib.bazel.bzlmod;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
import java.util.Optional;
import java.util.function.UnaryOperator;
import javax.annotation.Nullable;
/**
* Represents a node in the external dependency graph during module resolution (discovery &
* selection).
*
* <p>In particular, it represents a specific version of a module; there can be multiple {@link
* InterimModule}s in a dependency graph with the same name but with different versions (such as
* after discovery but before selection, or when there's a multiple_version_override in play).
*
* <p>Compared to {@link Module}, which is used after module resolution, this class holds some more
* information that's useful only during resolution, such as the {@code max_compatibility_level} for
* each dep, the {@code compatibility_level}, the {@code registry} the module comes from, etc.
*/
@AutoValue
@GenerateTypeAdapter
public abstract class InterimModule extends ModuleBase {
/**
* The compatibility level of the module, which essentially signifies the "major version" of the
* module in terms of SemVer.
*/
public abstract int getCompatibilityLevel();
/** List of bazel compatible versions that would run/fail this module */
public abstract ImmutableList<String> getBazelCompatibility();
/** The specification of a dependency. */
@AutoValue
public abstract static class DepSpec {
public abstract String getName();
public abstract Version getVersion();
public abstract int getMaxCompatibilityLevel();
public static DepSpec create(String name, Version version, int maxCompatibilityLevel) {
return new AutoValue_InterimModule_DepSpec(name, version, maxCompatibilityLevel);
}
public static DepSpec fromModuleKey(ModuleKey key) {
return create(key.getName(), key.getVersion(), -1);
}
public final ModuleKey toModuleKey() {
return ModuleKey.create(getName(), getVersion());
}
}
/**
* The resolved direct dependencies of this module, which can be either the original ones,
* overridden by a {@code single_version_override}, by a {@code multiple_version_override}, or by
* a {@link NonRegistryOverride} (the version will be ""). The key type is the repo name of the
* dep.
*/
public abstract ImmutableMap<String, DepSpec> getDeps();
/**
* The original direct dependencies of this module as they are declared in their MODULE file. The
* key type is the repo name of the dep.
*/
public abstract ImmutableMap<String, DepSpec> getOriginalDeps();
/**
* The registry where this module came from. Must be null iff the module has a {@link
* NonRegistryOverride}.
*/
@Nullable
public abstract Registry getRegistry();
/** Returns a {@link Builder} that starts out with the same fields as this object. */
abstract Builder toBuilder();
/** Returns a new, empty {@link Builder}. */
public static Builder builder() {
return new AutoValue_InterimModule.Builder()
.setName("")
.setVersion(Version.EMPTY)
.setKey(ModuleKey.ROOT)
.setCompatibilityLevel(0);
}
/**
* Returns a new {@link InterimModule} with all values in {@link #getDeps} transformed using the
* given function.
*/
public InterimModule withDepSpecsTransformed(UnaryOperator<DepSpec> transform) {
return toBuilder()
.setDeps(ImmutableMap.copyOf(Maps.transformValues(getDeps(), transform::apply)))
.build();
}
/** Builder type for {@link InterimModule}. */
@AutoValue.Builder
public abstract static class Builder {
/** Optional; defaults to the empty string. */
public abstract Builder setName(String value);
/** Optional; defaults to {@link Version#EMPTY}. */
public abstract Builder setVersion(Version value);
/** Optional; defaults to {@link ModuleKey#ROOT}. */
public abstract Builder setKey(ModuleKey value);
/** Optional; defaults to {@code 0}. */
public abstract Builder setCompatibilityLevel(int value);
/** Optional; defaults to {@link #setName}. */
public abstract Builder setRepoName(String value);
public abstract Builder setBazelCompatibility(ImmutableList<String> value);
abstract ImmutableList.Builder<String> bazelCompatibilityBuilder();
@CanIgnoreReturnValue
public final Builder addBazelCompatibilityValues(Iterable<String> values) {
bazelCompatibilityBuilder().addAll(values);
return this;
}
public abstract Builder setExecutionPlatformsToRegister(ImmutableList<String> value);
abstract ImmutableList.Builder<String> executionPlatformsToRegisterBuilder();
@CanIgnoreReturnValue
public final Builder addExecutionPlatformsToRegister(Iterable<String> values) {
executionPlatformsToRegisterBuilder().addAll(values);
return this;
}
public abstract Builder setToolchainsToRegister(ImmutableList<String> value);
abstract ImmutableList.Builder<String> toolchainsToRegisterBuilder();
@CanIgnoreReturnValue
public final Builder addToolchainsToRegister(Iterable<String> values) {
toolchainsToRegisterBuilder().addAll(values);
return this;
}
public abstract Builder setOriginalDeps(ImmutableMap<String, DepSpec> value);
public abstract Builder setDeps(ImmutableMap<String, DepSpec> value);
abstract ImmutableMap.Builder<String, DepSpec> depsBuilder();
@CanIgnoreReturnValue
public Builder addDep(String depRepoName, DepSpec depSpec) {
depsBuilder().put(depRepoName, depSpec);
return this;
}
abstract ImmutableMap.Builder<String, DepSpec> originalDepsBuilder();
@CanIgnoreReturnValue
public Builder addOriginalDep(String depRepoName, DepSpec depSpec) {
originalDepsBuilder().put(depRepoName, depSpec);
return this;
}
public abstract Builder setRegistry(Registry value);
public abstract Builder setExtensionUsages(ImmutableList<ModuleExtensionUsage> value);
abstract ImmutableList.Builder<ModuleExtensionUsage> extensionUsagesBuilder();
@CanIgnoreReturnValue
public Builder addExtensionUsage(ModuleExtensionUsage value) {
extensionUsagesBuilder().add(value);
return this;
}
abstract ModuleKey getKey();
abstract String getName();
abstract Optional<String> getRepoName();
abstract InterimModule autoBuild();
final InterimModule build() {
if (getRepoName().isEmpty()) {
setRepoName(getName());
}
return autoBuild();
}
}
/**
* Builds a {@link Module} from an {@link InterimModule}, discarding unnecessary fields and adding
* extra necessary ones (such as the repo spec).
*
* @param remoteRepoSpec the {@link RepoSpec} for the module obtained from a registry or null if
* the module has a non-registry override
*/
static Module toModule(
InterimModule interim, @Nullable ModuleOverride override, @Nullable RepoSpec remoteRepoSpec) {
return Module.builder()
.setName(interim.getName())
.setVersion(interim.getVersion())
.setKey(interim.getKey())
.setRepoName(interim.getRepoName())
.setExecutionPlatformsToRegister(interim.getExecutionPlatformsToRegister())
.setToolchainsToRegister(interim.getToolchainsToRegister())
.setDeps(ImmutableMap.copyOf(Maps.transformValues(interim.getDeps(), DepSpec::toModuleKey)))
.setRepoSpec(maybeAppendAdditionalPatches(remoteRepoSpec, override))
.setExtensionUsages(interim.getExtensionUsages())
.build();
}
private static RepoSpec maybeAppendAdditionalPatches(
@Nullable RepoSpec repoSpec, @Nullable ModuleOverride override) {
if (!(override instanceof SingleVersionOverride singleVersion)) {
return repoSpec;
}
if (singleVersion.getPatches().isEmpty()) {
return repoSpec;
}
ImmutableMap.Builder<String, Object> attrBuilder = ImmutableMap.builder();
attrBuilder.putAll(repoSpec.attributes().attributes());
attrBuilder.put("patches", singleVersion.getPatches());
attrBuilder.put("patch_cmds", singleVersion.getPatchCmds());
attrBuilder.put("patch_args", ImmutableList.of("-p" + singleVersion.getPatchStrip()));
return RepoSpec.builder()
.setBzlFile(repoSpec.bzlFile())
.setRuleClassName(repoSpec.ruleClassName())
.setAttributes(AttributeValues.create(attrBuilder.buildOrThrow()))
.build();
}
static UnaryOperator<DepSpec> applyOverrides(
ImmutableMap<String, ModuleOverride> overrides, String rootModuleName) {
return depSpec -> {
if (rootModuleName.equals(depSpec.getName())) {
return DepSpec.fromModuleKey(ModuleKey.ROOT);
}
Version newVersion = depSpec.getVersion();
@Nullable ModuleOverride override = overrides.get(depSpec.getName());
if (override instanceof NonRegistryOverride) {
newVersion = Version.EMPTY;
} else if (override instanceof SingleVersionOverride singleVersionOverride) {
Version overrideVersion = singleVersionOverride.getVersion();
if (!overrideVersion.isEmpty()) {
newVersion = overrideVersion;
}
}
return DepSpec.create(depSpec.getName(), newVersion, depSpec.getMaxCompatibilityLevel());
};
}
}