blob: af13cff8902421271b5ce3bdc0f57220d33509c1 [file] [log] [blame]
// Copyright 2021 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.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.devtools.build.docgen.annot.DocCategory;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.packages.LabelConverter;
import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps.Code;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.StarlarkValue;
import net.starlark.java.eval.Structure;
import net.starlark.java.spelling.SpellChecker;
/** A Starlark object representing a Bazel module in the external dependency graph. */
@StarlarkBuiltin(
name = "bazel_module",
category = DocCategory.BUILTIN,
doc = "Represents a Bazel module in the external dependency graph.")
public class StarlarkBazelModule implements StarlarkValue {
private final String name;
private final String version;
private final Tags tags;
private final boolean isRootModule;
@StarlarkBuiltin(
name = "bazel_module_tags",
category = DocCategory.BUILTIN,
doc =
"Contains the tags in a module for the module extension currently being processed. This"
+ " object has a field for each tag class of the extension, and the value of the"
+ " field is a list containing an object for each tag instance. This \"tag instance\""
+ " object in turn has a field for each attribute of the tag class.\n\n"
+ "When passed as positional arguments to <code>print()</code> or <code>fail()"
+ "</code>, tag instance objects turn into a meaningful string representation of the"
+ " form \"'install' tag at /home/user/workspace/MODULE.bazel:3:4\". This can be used"
+ " to construct error messages that point to the location of the tag in the module"
+ " file, e.g. <code>fail(\"Conflict between\", tag1, \"and\", tag2)</code>.")
static class Tags implements Structure {
private final ImmutableMap<String, StarlarkList<TypeCheckedTag>> typeCheckedTags;
private Tags(Map<String, StarlarkList<TypeCheckedTag>> typeCheckedTags) {
this.typeCheckedTags = ImmutableMap.copyOf(typeCheckedTags);
}
@Override
public boolean isImmutable() {
return true;
}
@Nullable
@Override
public Object getValue(String name) throws EvalException {
return typeCheckedTags.get(name);
}
@Override
public ImmutableCollection<String> getFieldNames() {
return typeCheckedTags.keySet();
}
@Nullable
@Override
public String getErrorMessageForUnknownField(String field) {
return "unknown tag class " + field;
}
}
private StarlarkBazelModule(String name, String version, Tags tags, boolean isRootModule) {
this.name = name;
this.version = version;
this.tags = tags;
this.isRootModule = isRootModule;
}
/**
* Creates a new {@link StarlarkBazelModule} object representing the given {@link AbridgedModule},
* with its scope limited to the given {@link ModuleExtension}. It'll be populated with the tags
* present in the given {@link ModuleExtensionUsage}. Any labels present in tags will be converted
* using the given {@link RepositoryMapping}.
*/
public static StarlarkBazelModule create(
AbridgedModule module,
ModuleExtension extension,
RepositoryMapping repoMapping,
@Nullable ModuleExtensionUsage usage)
throws ExternalDepsException {
LabelConverter labelConverter =
new LabelConverter(
PackageIdentifier.create(module.getCanonicalRepoName(), PathFragment.EMPTY_FRAGMENT),
repoMapping);
ImmutableList<Tag> tags = usage == null ? ImmutableList.of() : usage.getTags();
HashMap<String, ArrayList<TypeCheckedTag>> typeCheckedTags = new HashMap<>();
for (String tagClassName : extension.getTagClasses().keySet()) {
typeCheckedTags.put(tagClassName, new ArrayList<>());
}
for (Tag tag : tags) {
TagClass tagClass = extension.getTagClasses().get(tag.getTagName());
if (tagClass == null) {
throw ExternalDepsException.withMessage(
Code.BAD_MODULE,
"The module extension defined at %s does not have a tag class named %s, but its use is"
+ " attempted at %s%s",
extension.getLocation(),
tag.getTagName(),
tag.getLocation(),
SpellChecker.didYouMean(tag.getTagName(), extension.getTagClasses().keySet()));
}
// Now we need to type-check the attribute values and convert them into "build language types"
// (for example, String to Label).
typeCheckedTags
.get(tag.getTagName())
.add(TypeCheckedTag.create(tagClass, tag, labelConverter));
}
return new StarlarkBazelModule(
module.getName(),
module.getVersion().getOriginal(),
new Tags(Maps.transformValues(typeCheckedTags, StarlarkList::immutableCopyOf)),
module.getKey().equals(ModuleKey.ROOT));
}
@Override
public boolean isImmutable() {
return true;
}
@StarlarkMethod(name = "name", structField = true, doc = "The name of the module.")
public String getName() {
return name;
}
@StarlarkMethod(name = "version", structField = true, doc = "The version of the module.")
public String getVersion() {
return version;
}
@StarlarkMethod(
name = "tags",
structField = true,
doc = "The tags in the module related to the module extension currently being processed.")
public Tags getTags() {
return tags;
}
@StarlarkMethod(
name = "is_root",
structField = true,
doc = "Whether this module is the root module.")
public boolean isRoot() {
return isRootModule;
}
}