// Copyright 2015 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.Optional.absent; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.isEmpty; import com.google.common.base.Optional; 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.Artifact; import com.google.devtools.build.lib.actions.ResourceSet; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.CommandLine; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.CustomMultiArgv; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.util.LazyString; import java.util.List; /** * An action to run the protocol compiler to generate sources from .proto files. */ public final class ProtoCompileAction { private static final String MNEMONIC = "GenProto"; private static final ResourceSet GENPROTO_RESOURCE_SET = ResourceSet.createWithRamCpuIo(100, .1, .0); private final RuleContext ruleContext; private final SupportData supportData; private final String language; private final Iterable outputs; private final List prefixArguments; private final FilesToRunProvider langPluginTarget; private final FilesToRunProvider compilerTarget; private final List suffixArguments; public static class Builder { private RuleContext ruleContext; private SupportData supportData; private String language; private String langPrefix; private Iterable outputs; private String langParameter; private String langPluginName; private String langPluginParameter; private Supplier langPluginParameterSupplier; private boolean hasServices; public Builder setRuleContext(RuleContext ruleContext) { this.ruleContext = ruleContext; return this; } public Builder setSupportData(SupportData supportData) { this.supportData = supportData; return this; } public Builder setLanguage(String language) { this.language = language; return this; } public Builder setLangPrefix(String langPrefix) { this.langPrefix = langPrefix; return this; } public Builder setHasServices(boolean hasServices) { this.hasServices = hasServices; return this; } public Builder setOutputs(Iterable outputs) { this.outputs = outputs; return this; } public Builder setLangParameter(String langParameter) { this.langParameter = langParameter; return this; } public Builder setLangPluginName(String langPluginName) { this.langPluginName = langPluginName; return this; } public Builder setLangPluginParameter(String langPluginParameter) { this.langPluginParameter = langPluginParameter; return this; } public Builder setLangPluginParameterSupplier(Supplier langPluginParameterSupplier) { this.langPluginParameterSupplier = langPluginParameterSupplier; return this; } public Builder(RuleContext ruleContext, SupportData supportData, String language, String langPrefix, Iterable outputs) { this.ruleContext = ruleContext; this.supportData = supportData; this.language = language; this.langPrefix = langPrefix; this.outputs = outputs; } public Optional build() { checkState(langPluginParameter == null || langPluginParameterSupplier == null, "Only one of {langPluginParameter, langPluginParameterSupplier} should be set."); final Supplier langPluginParameter1 = langPluginParameter == null ? langPluginParameterSupplier : Suppliers.ofInstance(langPluginParameter); if (isEmpty(outputs)) { return absent(); } FilesToRunProvider langPluginTarget = null; List prefixArguments; if (langPluginName != null) { 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". langPluginTarget = ruleContext.getExecutablePrerequisite(langPluginName, Mode.HOST); if (ruleContext.hasErrors()) { return absent(); } LazyString lazyLangPlugingFlag = new LazyString() { @Override public String toString() { return String.format("--%s_out=%s", langPrefix, langPluginParameter1.get()); } }; prefixArguments = ImmutableList.of( String.format( "--plugin=protoc-gen-%s=%s", langPrefix, langPluginTarget.getExecutable().getExecPathString()), lazyLangPlugingFlag); } else { prefixArguments = (langParameter != null) ? ImmutableList.of(langParameter) : ImmutableList.of(); } List suffixArguments = hasServices ? ImmutableList.of() : ImmutableList.of("--disallow_services"); FilesToRunProvider compilerTarget = ruleContext.getExecutablePrerequisite("$compiler", Mode.HOST); if (ruleContext.hasErrors()) { return absent(); } return Optional.of( new ProtoCompileAction( ruleContext, supportData, language, suffixArguments, outputs, prefixArguments, langPluginTarget, compilerTarget)); } } /** * A convenience method to register an action, if it's present. * @param protoCompileActionOptional */ public static void registerAction(Optional protoCompileActionOptional) { if (protoCompileActionOptional.isPresent()) { protoCompileActionOptional.get().registerAction(); } } public ProtoCompileAction( RuleContext ruleContext, SupportData supportData, String language, List suffixArguments, Iterable outputs, List prefixArguments, FilesToRunProvider langPluginTarget, FilesToRunProvider compilerTarget) { this.ruleContext = ruleContext; this.supportData = supportData; this.language = language; this.suffixArguments = suffixArguments; this.outputs = outputs; this.prefixArguments = prefixArguments; this.langPluginTarget = langPluginTarget; this.compilerTarget = compilerTarget; } /** * Registers a proto compile action with the RuleContext. */ public void registerAction() { SpawnAction.Builder action = createAction(protoCompileCommandLine().build()); ruleContext.registerAction(action.build(ruleContext)); } public SpawnAction.Builder createAction(CommandLine commandLine) { SpawnAction.Builder builder = new SpawnAction.Builder().addTransitiveInputs(supportData.getTransitiveImports()); // We also depend on the strict protodeps result to ensure this is run. if (supportData.getUsedDirectDeps() != null) { builder.addInput(supportData.getUsedDirectDeps()); } if (langPluginTarget != null) { builder.addTool(langPluginTarget); } builder .addOutputs(outputs) .setResources(GENPROTO_RESOURCE_SET) .useDefaultShellEnvironment() .setExecutable(compilerTarget) .setCommandLine(commandLine) .setProgressMessage("Generating " + language + " proto_library " + ruleContext.getLabel()) .setMnemonic(MNEMONIC); return builder; } /* Commandline generator for protoc invocations. */ public CustomCommandLine.Builder protoCompileCommandLine() { CustomCommandLine.Builder arguments = CustomCommandLine.builder(); for (CharSequence charSequence : prefixArguments) { arguments.add(charSequence); } arguments.add(ruleContext.getFragment(ProtoConfiguration.class).protocOpts()); // Add include maps arguments.add( new CustomMultiArgv() { @Override public Iterable argv() { ImmutableList.Builder builder = ImmutableList.builder(); for (Artifact artifact : supportData.getTransitiveImports()) { builder.add( "-I" + artifact.getRootRelativePath().getPathString() + "=" + artifact.getExecPathString()); } return builder.build(); } }); for (Artifact src : supportData.getDirectProtoSources()) { arguments.addPath(src.getRootRelativePath()); } arguments.add(suffixArguments); return arguments; } }