blob: 0f58c658bbab3dc8f2ab260a4eeee0d3ff30896e [file] [log] [blame]
// Copyright 2016 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.rules.proto;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.isEmpty;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ParameterFile;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.util.LazyString;
import javax.annotation.Nullable;
/**
* Constructs actions to run the protocol compiler to generate sources from .proto files.
*/
public class ProtoCompileActionBuilder {
private static final String MNEMONIC = "GenProto";
private static final ResourceSet GENPROTO_RESOURCE_SET =
ResourceSet.createWithRamCpuIo(100, .1, .0);
private static final Action[] NO_ACTIONS = new Action[0];
private RuleContext ruleContext;
private SupportData supportData;
private String language;
private String langPrefix;
private Iterable<Artifact> outputs;
private String langParameter;
private String langPluginName;
private String langPluginParameter;
private Supplier<String> langPluginParameterSupplier;
private boolean hasServices;
private Iterable<String> additionalCommandLineArguments;
private Iterable<FilesToRunProvider> additionalTools;
/**
* Build a proto compiler commandline argument for use in setXParameter methods.
*/
public static String buildProtoArg(String arg, String value, Iterable<String> flags) {
return String.format("--%s=%s%s",
arg, (isEmpty(flags) ? "" : Joiner.on(',').join(flags) + ":"), value);
}
public ProtoCompileActionBuilder setRuleContext(RuleContext ruleContext) {
this.ruleContext = ruleContext;
return this;
}
public ProtoCompileActionBuilder setSupportData(SupportData supportData) {
this.supportData = supportData;
return this;
}
public ProtoCompileActionBuilder setLanguage(String language) {
this.language = language;
return this;
}
public ProtoCompileActionBuilder setLangPrefix(String langPrefix) {
this.langPrefix = langPrefix;
return this;
}
public ProtoCompileActionBuilder allowServices(boolean hasServices) {
this.hasServices = hasServices;
return this;
}
public ProtoCompileActionBuilder setOutputs(Iterable<Artifact> outputs) {
this.outputs = outputs;
return this;
}
public ProtoCompileActionBuilder setLangParameter(String langParameter) {
this.langParameter = langParameter;
return this;
}
public ProtoCompileActionBuilder setLangPluginName(String langPluginName) {
this.langPluginName = langPluginName;
return this;
}
public ProtoCompileActionBuilder setLangPluginParameter(String langPluginParameter) {
this.langPluginParameter = langPluginParameter;
return this;
}
public ProtoCompileActionBuilder setLangPluginParameterSupplier(
Supplier<String> langPluginParameterSupplier) {
this.langPluginParameterSupplier = langPluginParameterSupplier;
return this;
}
public ProtoCompileActionBuilder setAdditionalCommandLineArguments(
Iterable<String> additionalCmdLine) {
this.additionalCommandLineArguments = additionalCmdLine;
return this;
}
public ProtoCompileActionBuilder setAdditionalTools(
Iterable<FilesToRunProvider> additionalTools) {
this.additionalTools = additionalTools;
return this;
}
public ProtoCompileActionBuilder(
RuleContext ruleContext,
SupportData supportData,
String language,
String langPrefix,
Iterable<Artifact> outputs) {
this.ruleContext = ruleContext;
this.supportData = supportData;
this.language = language;
this.langPrefix = langPrefix;
this.outputs = outputs;
}
/**
* Static class to avoid keeping a reference to this builder after build() is called.
*/
private static class LazyLangPluginFlag extends LazyString {
private final String langPrefix;
private final Supplier<String> langPluginParameter1;
LazyLangPluginFlag(String langPrefix, Supplier<String> langPluginParameter1) {
this.langPrefix = langPrefix;
this.langPluginParameter1 = langPluginParameter1;
}
@Override
public String toString() {
return String.format("--%s_out=%s", langPrefix, langPluginParameter1.get());
}
}
public Action[] build() {
checkState(
langPluginParameter == null || langPluginParameterSupplier == null,
"Only one of {langPluginParameter, langPluginParameterSupplier} should be set.");
if (isEmpty(outputs)) {
return NO_ACTIONS;
}
try {
return createAction().build(ruleContext);
} catch (MissingPrerequisiteException e) {
return NO_ACTIONS;
}
}
private SpawnAction.Builder createAction() {
SpawnAction.Builder result =
new SpawnAction.Builder().addTransitiveInputs(supportData.getTransitiveImports());
// We also depend on the strict protodeps result to ensure this is run.
if (supportData.getUsedDirectDeps() != null) {
result.addInput(supportData.getUsedDirectDeps());
}
FilesToRunProvider langPluginTarget = getLangPluginTarget();
if (langPluginTarget != null) {
result.addTool(langPluginTarget);
}
FilesToRunProvider compilerTarget =
ruleContext.getExecutablePrerequisite(":proto_compiler", RuleConfiguredTarget.Mode.HOST);
if (ruleContext.hasErrors()) {
throw new MissingPrerequisiteException();
}
if (this.additionalTools != null) {
for (FilesToRunProvider tool : additionalTools) {
result.addTool(tool);
}
}
result
.useParameterFile(ParameterFile.ParameterFileType.UNQUOTED)
.addOutputs(outputs)
.setResources(GENPROTO_RESOURCE_SET)
.useDefaultShellEnvironment()
.setExecutable(compilerTarget)
.setCommandLine(createProtoCompilerCommandLine().build())
.setProgressMessage("Generating " + language + " proto_library " + ruleContext.getLabel())
.setMnemonic(MNEMONIC);
return result;
}
@Nullable
private FilesToRunProvider getLangPluginTarget() {
if (langPluginName == null) {
return null;
}
FilesToRunProvider result =
ruleContext.getExecutablePrerequisite(langPluginName, RuleConfiguredTarget.Mode.HOST);
if (ruleContext.hasErrors()) {
throw new MissingPrerequisiteException();
}
return result;
}
/**
* Commandline generator for protoc invocations.
*/
private CustomCommandLine.Builder createProtoCompilerCommandLine() {
CustomCommandLine.Builder result = CustomCommandLine.builder();
if (langPluginName == null) {
if (langParameter != null) {
result.add(langParameter);
}
} else {
FilesToRunProvider langPluginTarget = getLangPluginTarget();
Supplier<String> langPluginParameter1 =
langPluginParameter == null
? langPluginParameterSupplier
: Suppliers.ofInstance(langPluginParameter);
Preconditions.checkArgument(langParameter == null);
Preconditions.checkArgument(langPluginParameter1 != null);
// We pass a separate langPluginName as there are plugins that cannot be overridden
// and thus we have to deal with "$xx_plugin" and "xx_plugin".
result.add(
String.format(
"--plugin=protoc-gen-%s=%s",
langPrefix,
langPluginTarget.getExecutable().getExecPathString()));
result.add(new LazyLangPluginFlag(langPrefix, langPluginParameter1));
}
result.add(ruleContext.getFragment(ProtoConfiguration.class).protocOpts());
// Add include maps
result.add(new ProtoCommandLineArgv(supportData.getTransitiveImports()));
for (Artifact src : supportData.getDirectProtoSources()) {
result.addPath(src.getRootRelativePath());
}
if (!hasServices) {
result.add("--disallow_services");
}
if (additionalCommandLineArguments != null) {
result.add(additionalCommandLineArguments);
}
return result;
}
/**
* Static inner class since these objects live into the execution phase and so they must not
* keep alive references to the surrounding analysis-phase objects.
*/
private static class ProtoCommandLineArgv extends CustomCommandLine.CustomMultiArgv {
private final Iterable<Artifact> transitiveImports;
ProtoCommandLineArgv(Iterable<Artifact> transitiveImports) {
this.transitiveImports = transitiveImports;
}
@Override
public Iterable<String> argv() {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (Artifact artifact : transitiveImports) {
builder.add(
"-I"
+ artifact.getRootRelativePath().getPathString()
+ "="
+ artifact.getExecPathString());
}
return builder.build();
}
}
/**
* Signifies that a prerequisite could not be satisfied.
*/
private static class MissingPrerequisiteException extends RuntimeException {}
}