blob: b92d5f5eeda7ed141f455d270ecea1ce99330dfc [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.modcommand;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionId;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModuleArg.ModuleArgConverter;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.Label.RepoContext;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.server.FailureDetails.ModCommand.Code;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Converters.CommaSeparatedNonEmptyOptionListConverter;
import com.google.devtools.common.options.OptionsParsingException;
import java.util.Optional;
/**
* Represents a reference to a module extension, parsed from a command-line argument in the form of
* {@code <module><bzl_file_label>%<extension_name>}. The {@code <module>} part is parsed as a
* {@link ModuleArg}. Valid examples include {@code @rules_java//java:extensions.bzl%toolchains},
* {@code rules_java@6.1.1//java:extensions.bzl%toolchains}, etc.
*/
@AutoValue
public abstract class ExtensionArg {
public static ExtensionArg create(
ModuleArg moduleArg, String repoRelativeBzlLabel, String extensionName) {
return new AutoValue_ExtensionArg(moduleArg, repoRelativeBzlLabel, extensionName);
}
public abstract ModuleArg moduleArg();
public abstract String repoRelativeBzlLabel();
public abstract String extensionName();
/** Resolves this {@link ExtensionArg} to a {@link ModuleExtensionId}. */
public final ModuleExtensionId resolveToExtensionId(
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex,
ImmutableMap<ModuleKey, AugmentedModule> depGraph,
ImmutableBiMap<String, ModuleKey> baseModuleDeps,
ImmutableBiMap<String, ModuleKey> baseModuleUnusedDeps)
throws InvalidArgumentException {
ImmutableSet<ModuleKey> refModules =
moduleArg()
.resolveToModuleKeys(
modulesIndex,
depGraph,
baseModuleDeps,
baseModuleUnusedDeps,
/* includeUnused= */ false,
/* warnUnused= */ false);
if (refModules.size() != 1) {
throw new InvalidArgumentException(
String.format(
"Module %s, as part of the extension specifier, should represent exactly one module"
+ " version. Choose one of: %s.",
moduleArg(), refModules),
Code.INVALID_ARGUMENTS);
}
ModuleKey key = Iterables.getOnlyElement(refModules);
try {
Label label =
Label.parseWithRepoContext(
repoRelativeBzlLabel(),
RepoContext.of(
key.getCanonicalRepoName(),
// Intentionally allow no repo mapping here: it's a repo-relative label!
RepositoryMapping.create(ImmutableMap.of(), key.getCanonicalRepoName())));
// TODO(wyv): support isolated extension usages?
return ModuleExtensionId.create(label, extensionName(), Optional.empty());
} catch (LabelSyntaxException e) {
throw new InvalidArgumentException(
String.format("bad label format in %s: %s", repoRelativeBzlLabel(), e.getMessage()),
Code.INVALID_ARGUMENTS,
e);
}
}
@Override
public final String toString() {
return moduleArg() + repoRelativeBzlLabel() + "%" + extensionName();
}
/** Converter for {@link ExtensionArg}. */
public static class ExtensionArgConverter extends Converter.Contextless<ExtensionArg> {
public static final ExtensionArgConverter INSTANCE = new ExtensionArgConverter();
@Override
public ExtensionArg convert(String input) throws OptionsParsingException {
int slashIdx = input.indexOf('/');
if (slashIdx < 0) {
throw new OptionsParsingException("Invalid argument " + input + ": missing .bzl label");
}
int percentIdx = input.indexOf('%');
if (percentIdx < slashIdx) {
throw new OptionsParsingException("Invalid argument " + input + ": missing extension name");
}
ModuleArg moduleArg = ModuleArgConverter.INSTANCE.convert(input.substring(0, slashIdx));
return ExtensionArg.create(
moduleArg, input.substring(slashIdx, percentIdx), input.substring(percentIdx + 1));
}
@Override
public String getTypeDescription() {
return "an <extension> identifier in the format of <module><bzl_label>%<extension_name>";
}
}
/** Converter for a comma-separated list of {@link ExtensionArg}s. */
public static class CommaSeparatedExtensionArgListConverter
extends Converter.Contextless<ImmutableList<ExtensionArg>> {
@Override
public ImmutableList<ExtensionArg> convert(String input) throws OptionsParsingException {
ImmutableList<String> args = new CommaSeparatedNonEmptyOptionListConverter().convert(input);
ImmutableList.Builder<ExtensionArg> extensionArgs = new ImmutableList.Builder<>();
for (String arg : args) {
extensionArgs.add(ExtensionArgConverter.INSTANCE.convert(arg));
}
return extensionArgs.build();
}
@Override
public String getTypeDescription() {
return "a comma-separated list of <extension>s";
}
}
}