blob: 9ffa35c394abb2603ee53e0695906d02eca402ea [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.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException;
import com.google.devtools.build.lib.packages.StarlarkNativeModule.ExistingRulesShouldBeNoOp;
import java.util.HashMap;
import java.util.Map;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.Location;
/**
* A context object that should be stored in a {@link StarlarkThread} for use during module
* extension evaluation.
*/
public final class ModuleExtensionEvalStarlarkThreadContext {
public void storeInThread(StarlarkThread thread) {
thread.setThreadLocal(ModuleExtensionEvalStarlarkThreadContext.class, this);
// The following is just a hack; see documentation there for an explanation.
thread.setThreadLocal(ExistingRulesShouldBeNoOp.class, new ExistingRulesShouldBeNoOp());
}
public static ModuleExtensionEvalStarlarkThreadContext from(StarlarkThread thread) {
return thread.getThreadLocal(ModuleExtensionEvalStarlarkThreadContext.class);
}
@AutoValue
abstract static class RepoSpecAndLocation {
abstract RepoSpec getRepoSpec();
abstract Location getLocation();
static RepoSpecAndLocation create(RepoSpec repoSpec, Location location) {
return new AutoValue_ModuleExtensionEvalStarlarkThreadContext_RepoSpecAndLocation(
repoSpec, location);
}
}
private final String repoPrefix;
private final PackageIdentifier basePackageId;
private final RepositoryMapping repoMapping;
private final BlazeDirectories directories;
private final ExtendedEventHandler eventHandler;
private final Map<String, RepoSpecAndLocation> generatedRepos = new HashMap<>();
public ModuleExtensionEvalStarlarkThreadContext(
String repoPrefix,
PackageIdentifier basePackageId,
RepositoryMapping repoMapping,
BlazeDirectories directories,
ExtendedEventHandler eventHandler) {
this.repoPrefix = repoPrefix;
this.basePackageId = basePackageId;
this.repoMapping = repoMapping;
this.directories = directories;
this.eventHandler = eventHandler;
}
public void createRepo(StarlarkThread thread, Dict<String, Object> kwargs, RuleClass ruleClass)
throws InterruptedException, EvalException {
Object nameValue = kwargs.getOrDefault("name", Starlark.NONE);
if (!(nameValue instanceof String)) {
throw Starlark.errorf(
"expected string for attribute 'name', got '%s'", Starlark.type(nameValue));
}
String name = (String) nameValue;
RepositoryName.validateUserProvidedRepoName(name);
RepoSpecAndLocation conflict = generatedRepos.get(name);
if (conflict != null) {
throw Starlark.errorf(
"A repo named %s is already generated by this module extension at %s",
name, conflict.getLocation());
}
String prefixedName = repoPrefix + name;
try {
Rule rule =
BzlmodRepoRuleCreator.createRule(
basePackageId,
repoMapping,
directories,
thread.getSemantics(),
eventHandler,
"RepositoryRuleFunction.createRule",
ruleClass,
Maps.transformEntries(kwargs, (k, v) -> k.equals("name") ? prefixedName : v));
Map<String, Object> attributes = Maps.transformEntries(kwargs, (k, v) -> rule.getAttr(k));
String bzlFile = ruleClass.getRuleDefinitionEnvironmentLabel().getUnambiguousCanonicalForm();
RepoSpec repoSpec =
RepoSpec.builder()
.setBzlFile(bzlFile)
.setRuleClassName(ruleClass.getName())
.setAttributes(AttributeValues.create(attributes))
.build();
generatedRepos.put(name, RepoSpecAndLocation.create(repoSpec, thread.getCallerLocation()));
} catch (InvalidRuleException | NoSuchPackageException e) {
throw Starlark.errorf("%s", e.getMessage());
}
}
/**
* Returns the repos generated by the extension so far. The key is the "internal name" (as
* specified by the extension) of the repo, and the value is the package containing (only) the
* repo rule.
*/
public ImmutableMap<String, RepoSpec> getGeneratedRepoSpecs() {
return ImmutableMap.copyOf(
Maps.transformValues(generatedRepos, RepoSpecAndLocation::getRepoSpec));
}
}