blob: cda88af60cc318dbd6a9d2fed320ad8f62e15f64 [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.truth.Truth.assertThat;
import static com.google.devtools.build.lib.rules.proto.ProtoCompileActionBuilder.registerActions;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
import com.google.devtools.build.lib.actions.ArtifactRoot.RootType;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.actions.util.LabelArtifactOwner;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.util.MockProtoSupport;
import com.google.devtools.build.lib.rules.proto.ProtoCompileActionBuilder.Services;
import com.google.devtools.build.lib.rules.proto.ProtoCompileActionBuilder.ToolchainInvocation;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.util.OnDemandString;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import javax.annotation.Nullable;
import net.starlark.java.eval.StarlarkValue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link ProtoCompileActionBuilder}. */
@RunWith(JUnit4.class)
public class ProtoCompileActionBuilderTest extends BuildViewTestCase {
private static final InMemoryFileSystem FILE_SYSTEM =
new InMemoryFileSystem(DigestHashFunction.SHA256);
private final ArtifactRoot sourceRoot =
ArtifactRoot.asSourceRoot(Root.fromPath(FILE_SYSTEM.getPath("/")));
private final ArtifactRoot derivedRoot =
ArtifactRoot.asDerivedRoot(FILE_SYSTEM.getPath("/"), RootType.Output, "out");
private AnalysisTestUtil.CollectingAnalysisEnvironment collectingAnalysisEnvironment;
private Artifact out;
@Before
public final void setup() throws Exception {
MockProtoSupport.setup(mockToolsConfig);
collectingAnalysisEnvironment =
new AnalysisTestUtil.CollectingAnalysisEnvironment(getTestAnalysisEnvironment());
scratch.file(
"foo/BUILD",
"package(features = ['-feature'])",
"proto_library(name = 'bar')",
"exports_files(['out'])");
out = getBinArtifactWithNoOwner("out");
}
private ProtoSource protoSource(String importPath) {
return protoSource(artifact("//:dont-care", importPath));
}
private ProtoSource protoSource(Artifact protoSource) {
return protoSource(protoSource, PathFragment.EMPTY_FRAGMENT);
}
private ProtoSource protoSource(Artifact protoSource, PathFragment sourceRoot) {
return new ProtoSource(protoSource, sourceRoot);
}
private ProtoInfo protoInfo(
ImmutableList<ProtoSource> directProtoSources,
ImmutableList<ProtoSource> transitiveProtoSources,
ImmutableList<ProtoSource> publicImportProtoSources,
ImmutableList<ProtoSource> strictImportableSources) {
return new ProtoInfo(
/* directSources */ directProtoSources,
/* directProtoSourceRoot */ PathFragment.EMPTY_FRAGMENT,
/* transitiveSources */ NestedSetBuilder.wrap(Order.STABLE_ORDER, transitiveProtoSources),
/* transitiveProtoSources */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* transitiveProtoSourceRoots */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* strictImportableProtoSourcesForDependents */ NestedSetBuilder.emptySet(
Order.STABLE_ORDER),
/* directDescriptorSet */ artifact("//:direct-descriptor-set", "direct-descriptor-set"),
/* transitiveDescriptorSets */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* exportedSources */ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
/* strictImportableSources */ NestedSetBuilder.wrap(
Order.STABLE_ORDER, strictImportableSources),
/* publicImportSources */ NestedSetBuilder.wrap(
Order.STABLE_ORDER, publicImportProtoSources));
}
@Test
public void commandLine_basic() throws Exception {
FilesToRunProvider plugin =
new FilesToRunProvider(
NestedSetBuilder.emptySet(Order.STABLE_ORDER),
null /* runfilesSupport */,
artifact("//:dont-care", "protoc-gen-javalite.exe"));
ProtoLangToolchainProvider toolchainNoPlugin =
ProtoLangToolchainProvider.create(
"--java_out=param1,param2:%s",
/* pluginFormatFlag= */ null,
/* pluginExecutable= */ null,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
ProtoLangToolchainProvider toolchainWithPlugin =
ProtoLangToolchainProvider.create(
"--PLUGIN_pluginName_out=param3,param4:%s",
/* pluginFormatFlag= */ "--plugin=protoc-gen-PLUGIN_pluginName=%s",
plugin,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
useConfiguration("--strict_proto_deps=OFF");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
ImmutableList.of(
new ToolchainInvocation("dontcare_because_no_plugin", toolchainNoPlugin, "foo.srcjar"),
new ToolchainInvocation("pluginName", toolchainWithPlugin, "bar.srcjar")),
protoInfo(
/* directProtoSources */ ImmutableList.of(protoSource("source_file.proto")),
/* transitiveProtoSources */ ImmutableList.of(
protoSource("import1.proto"), protoSource("import2.proto")),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ ImmutableList.of()),
ImmutableList.of(out),
"dontcare_because_no_plugin",
Services.ALLOW);
ImmutableList<String> cmdLine =
allArgsForAction((SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
assertThat(cmdLine)
.containsExactly(
"--java_out=param1,param2:foo.srcjar",
"--PLUGIN_pluginName_out=param3,param4:bar.srcjar",
"--plugin=protoc-gen-PLUGIN_pluginName=protoc-gen-javalite.exe",
"-Iimport1.proto=import1.proto",
"-Iimport2.proto=import2.proto",
"source_file.proto")
.inOrder();
}
@Test
public void commandline_derivedArtifact() throws Exception {
// Verify that the command line contains the correct path to a generated protocol buffers.
useConfiguration("--strict_proto_deps=OFF");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
/* toolchainInvocations= */ ImmutableList.of(),
protoInfo(
/* directProtoSources */ ImmutableList.of(
protoSource(derivedArtifact("source_file.proto"))),
/* transitiveProtoSources */ ImmutableList.of(),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ ImmutableList.of()),
ImmutableList.of(out),
"dontcare_because_no_plugin",
Services.ALLOW);
ImmutableList<String> cmdLine =
allArgsForAction((SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
assertThat(cmdLine).containsExactly("out/source_file.proto");
}
@Test
public void commandLine_strictDeps() throws Exception {
ProtoLangToolchainProvider toolchain =
ProtoLangToolchainProvider.create(
"--java_out=param1,param2:%s",
/* pluginFormatFlag= */ null,
/* pluginExecutable= */ null,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
ImmutableList.of(new ToolchainInvocation("dontcare", toolchain, "foo.srcjar")),
protoInfo(
/* directProtoSources */ ImmutableList.of(protoSource("source_file.proto")),
/* transitiveProtoSources */ ImmutableList.of(
protoSource("import1.proto"), protoSource("import2.proto")),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ ImmutableList.of(protoSource("import1.proto"))),
ImmutableList.of(out),
"dontcare_because_no_plugin",
Services.ALLOW);
ImmutableList<String> cmdLine =
allArgsForAction((SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
assertThat(cmdLine)
.containsExactly(
"--java_out=param1,param2:foo.srcjar",
"-Iimport1.proto=import1.proto",
"-Iimport2.proto=import2.proto",
"source_file.proto")
.inOrder();
}
@Test
public void commandLine_exports() throws Exception {
ProtoLangToolchainProvider toolchain =
ProtoLangToolchainProvider.create(
"--java_out=param1,param2:%s",
/* pluginFormatFlag= */ null,
/* pluginExecutable= */ null,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
useConfiguration("--strict_proto_deps=OFF");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
ImmutableList.of(new ToolchainInvocation("dontcare", toolchain, "foo.srcjar")),
protoInfo(
/* directProtoSources */ ImmutableList.of(protoSource("source_file.proto")),
/* transitiveProtoSources */ ImmutableList.of(
protoSource("import1.proto"), protoSource("import2.proto")),
/* publicImportProtoSources */ ImmutableList.of(protoSource("export1.proto")),
/* strictImportableSources */ ImmutableList.of()),
ImmutableList.of(out),
"dontcare_because_no_plugin",
Services.ALLOW);
ImmutableList<String> cmdLine =
allArgsForAction((SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
assertThat(cmdLine)
.containsExactly(
"--java_out=param1,param2:foo.srcjar",
"-Iimport1.proto=import1.proto",
"-Iimport2.proto=import2.proto",
"source_file.proto")
.inOrder();
}
@Test
public void otherParameters() throws Exception {
useConfiguration("--protocopt=--foo", "--protocopt=--bar");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
ImmutableList.of(),
protoInfo(
/* directProtoSources */ ImmutableList.of(),
/* transitiveProtoSources */ ImmutableList.of(),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ ImmutableList.of()),
ImmutableList.of(out),
"dontcare",
Services.DISALLOW);
ImmutableList<String> cmdLine =
allArgsForAction((SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
assertThat(cmdLine).containsAtLeast("--disallow_services", "--foo", "--bar");
}
private static class InterceptOnDemandString extends OnDemandString implements StarlarkValue {
boolean hasBeenCalled;
@Override
public String toString() {
hasBeenCalled = true;
return "mu";
}
}
@Test
public void outReplacementAreLazilyEvaluated() throws Exception {
InterceptOnDemandString outReplacement = new InterceptOnDemandString();
ProtoLangToolchainProvider toolchain =
ProtoLangToolchainProvider.create(
"--java_out=param1,param2:%s",
/* pluginFormatFlag= */ null,
/* pluginExecutable= */ null,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
ImmutableList.of(new ToolchainInvocation("pluginName", toolchain, outReplacement)),
protoInfo(
/* directProtoSources */ ImmutableList.of(),
/* transitiveProtoSources */ ImmutableList.of(),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ ImmutableList.of()),
ImmutableList.of(out),
"flavour",
Services.ALLOW);
assertThat(outReplacement.hasBeenCalled).isFalse();
allArgsForAction((SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
assertThat(outReplacement.hasBeenCalled).isTrue();
}
/**
* Tests that if the same invocation-name is specified by more than one invocation,
* ProtoCompileActionBuilder throws an exception.
*/
@Test
public void exceptionIfSameName() throws Exception {
ProtoLangToolchainProvider toolchain1 =
ProtoLangToolchainProvider.create(
"dontcare=%s",
/* pluginFormatFlag= */ null,
/* pluginExecutable= */ null,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
ProtoLangToolchainProvider toolchain2 =
ProtoLangToolchainProvider.create(
"dontcare=%s",
/* pluginFormatFlag= */ null,
/* pluginExecutable= */ null,
/* runtime= */ mock(TransitiveInfoCollection.class),
/* providedProtoSources= */ ImmutableList.of(),
/* protoc= */ FilesToRunProvider.EMPTY,
/* protocOpts= */ ImmutableList.of(),
/* progressMessage = */ "",
/* mnemonic= */ "");
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
IllegalStateException e =
assertThrows(
IllegalStateException.class,
() ->
registerActions(
ruleContext,
ImmutableList.of(
new ToolchainInvocation("pluginName", toolchain1, "outReplacement"),
new ToolchainInvocation("pluginName", toolchain2, "outReplacement")),
protoInfo(
/* directProtoSources */ ImmutableList.of(),
/* transitiveProtoSources */ ImmutableList.of(),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ ImmutableList.of()),
ImmutableList.of(out),
"dontcare",
Services.ALLOW));
assertThat(e)
.hasMessageThat()
.isEqualTo(
"Invocation name pluginName appears more than once. "
+ "This could lead to incorrect proto-compiler behavior");
}
@Test
public void testProtoCommandLineArgv_noImportable() throws Exception {
assertThat(
protoArgv(
/* transitiveSources */ ImmutableList.of(
protoSource(derivedArtifact("foo.proto"), derivedRoot.getExecPath())),
/* importableProtoSources */ null))
.contains("-Ifoo.proto=out/foo.proto");
}
@Test
public void testProtoCommandLineArgv_singleImportable() throws Exception {
assertThat(
protoArgv(
/* transitiveSources */ ImmutableList.of(
protoSource(derivedArtifact("foo.proto"), derivedRoot.getExecPath())),
/* importableProtoSources */ ImmutableList.of(
protoSource(derivedArtifact("foo.proto"), derivedRoot.getExecPath()))))
.contains("-Ifoo.proto=out/foo.proto");
}
@Test
public void testProtoCommandLineArgv_doubleImportable() throws Exception {
assertThat(
protoArgv(
/* transitiveSources */ ImmutableList.of(
protoSource(derivedArtifact("foo.proto"), derivedRoot.getExecPath())),
/* importableProtoSources */ ImmutableList.of(
protoSource(derivedArtifact("foo.proto"), derivedRoot.getExecPath()),
protoSource(derivedArtifact("bar.proto"), derivedRoot.getExecPath()))))
.contains("-Ifoo.proto=out/foo.proto");
}
/**
* Include-maps are the -Ivirtual=physical arguments passed to proto-compiler. When including a
* file named 'foo/bar.proto' from an external repository 'bla', the include-map should be
* -Ifoo/bar.proto=external/bla/foo/bar.proto. That is - 'virtual' should be the path relative to
* the external repo root, and physical should be the physical file location.
*/
@Test
public void testIncludeMapsOfExternalFiles() throws Exception {
assertThat(
protoArgv(
/* transitiveSources */ ImmutableList.of(
protoSource(
artifact("@bla//foo:bar", "external/bla/foo/bar.proto"),
PathFragment.create("external/bla"))),
/* importableProtoSources */ ImmutableList.of()))
.contains("-Ifoo/bar.proto=external/bla/foo/bar.proto");
}
@Test
public void directDependenciesOnExternalFiles() throws Exception {
Artifact protoSource = artifact("@bla//foo:bar", "external/bla/foo/bar.proto");
assertThat(
protoArgv(
/* transitiveSources */ ImmutableList.of(
protoSource(protoSource, PathFragment.create("external/bla"))),
/* importableProtoSources */ ImmutableList.of(
protoSource(protoSource, PathFragment.create("external/bla")))))
.contains("-Ifoo/bar.proto=external/bla/foo/bar.proto");
}
private Artifact artifact(String ownerLabel, String path) {
return new Artifact.SourceArtifact(
sourceRoot,
sourceRoot.getExecPath().getRelative(path),
new LabelArtifactOwner(Label.parseAbsoluteUnchecked(ownerLabel)));
}
/** Creates a dummy artifact with the given path, that actually resides in /out/<path>. */
private Artifact derivedArtifact(String path) {
Artifact.DerivedArtifact derivedArtifact =
DerivedArtifact.create(
derivedRoot,
derivedRoot.getExecPath().getRelative(path),
ActionsTestUtil.NULL_ARTIFACT_OWNER);
derivedArtifact.setGeneratingActionKey(ActionsTestUtil.NULL_ACTION_LOOKUP_DATA);
return derivedArtifact;
}
private ImmutableList<String> protoArgv(
Iterable<ProtoSource> transitiveSources,
@Nullable Iterable<ProtoSource> importableProtoSources)
throws Exception {
RuleContext ruleContext =
getRuleContext(getConfiguredTarget("//foo:bar"), collectingAnalysisEnvironment);
registerActions(
ruleContext,
ImmutableList.of(),
protoInfo(
/* directProtoSources */ ImmutableList.of(),
/* transitiveProtoSources */ ImmutableList.copyOf(transitiveSources),
/* publicImportProtoSources */ ImmutableList.of(),
/* strictImportableSources */ importableProtoSources == null
? ImmutableList.of()
: ImmutableList.copyOf(importableProtoSources)),
ImmutableList.of(out),
"dontcare",
Services.DISALLOW);
return allArgsForAction(
(SpawnAction) collectingAnalysisEnvironment.getRegisteredActions().get(0));
}
@Test
public void testEstimateResourceConsumptionLocal() throws Exception {
assertThat(
new ProtoCompileActionBuilder.ProtoCompileResourceSetBuilder()
.buildResourceSet(OS.DARWIN, 0))
.isEqualTo(ResourceSet.createWithRamCpu(25, 1));
assertThat(
new ProtoCompileActionBuilder.ProtoCompileResourceSetBuilder()
.buildResourceSet(OS.LINUX, 2))
.isEqualTo(ResourceSet.createWithRamCpu(25.3, 1));
}
}