// 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.collect.nestedset.Order.STABLE_ORDER; import static com.google.devtools.build.lib.rules.proto.ProtoCompileActionBuilder.createCommandLineFromToolchains; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.actions.util.LabelArtifactOwner; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.rules.proto.ProtoCompileActionBuilder.ToolchainInvocation; import com.google.devtools.build.lib.util.LazyString; import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import javax.annotation.Nullable; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ProtoCompileActionBuilderTest { private static final InMemoryFileSystem FILE_SYSTEM = new InMemoryFileSystem(); private final ArtifactRoot root = ArtifactRoot.asSourceRoot(Root.fromPath(FILE_SYSTEM.getPath("/"))); private final ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(FILE_SYSTEM.getPath("/"), FILE_SYSTEM.getPath("/out")); @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:$(OUT)", /* pluginExecutable= */ null, /* runtime= */ mock(TransitiveInfoCollection.class), /* blacklistedProtos= */ NestedSetBuilder.emptySet(STABLE_ORDER)); ProtoLangToolchainProvider toolchainWithPlugin = ProtoLangToolchainProvider.create( "--$(PLUGIN_OUT)=param3,param4:$(OUT)", plugin, /* runtime= */ mock(TransitiveInfoCollection.class), /* blacklistedProtos= */ NestedSetBuilder.emptySet(STABLE_ORDER)); SupportData supportData = SupportData.create( Predicates.alwaysFalse(), ImmutableList.of(artifact("//:dont-care", "source_file.proto")), /* protosInDirectDeps= */ NestedSetBuilder.emptySet(STABLE_ORDER), NestedSetBuilder.create( STABLE_ORDER, artifact("//:dont-care", "import1.proto"), artifact("//:dont-care", "import2.proto")), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* protoSourceRoot= */ "", /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* hasProtoSources= */ true, /* protosInExports= */ null, /* exportedProtoSourceRoots= */ null); CustomCommandLine cmdLine = createCommandLineFromToolchains( ImmutableList.of( new ToolchainInvocation( "dontcare_because_no_plugin", toolchainNoPlugin, "foo.srcjar"), new ToolchainInvocation("pluginName", toolchainWithPlugin, "bar.srcjar")), supportData.getDirectProtoSources(), supportData.getTransitiveImports(), /* transitiveProtoPathFlags= */ NestedSetBuilder.stableOrder().build(), /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* exportedProtoSourceRoots= */ null, /* protosInDirectDeps= */ null, /* protosInExports= */ null, Label.parseAbsoluteUnchecked("//foo:bar"), /* allowServices= */ true, /* protocOpts= */ ImmutableList.of()); assertThat(cmdLine.arguments()) .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() { // Verify that the command line contains the correct path to a generated protocol buffers. SupportData supportData = SupportData.create( Predicates.alwaysFalse(), ImmutableList.of(derivedArtifact("//:dont-care", "source_file.proto")), /* protosInDirectDeps= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* transitiveImports= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* protoSourceRoot= */ "", /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* hasProtoSources= */ true, /* protosInExports= */ null, /* exportedProtoSourceRoots= */ null); CustomCommandLine cmdLine = createCommandLineFromToolchains( /* toolchainInvocations= */ ImmutableList.of(), supportData.getDirectProtoSources(), supportData.getTransitiveImports(), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* directProtoSourceRoots= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* exportedProtoSourceRoots= */ null, /* protosInDirectDeps= */ null, /* protosInExports= */ null, Label.parseAbsoluteUnchecked("//foo:bar"), /* allowServices= */ true, /* protocOpts= */ ImmutableList.of()); assertThat(cmdLine.arguments()).containsExactly("out/source_file.proto"); } @Test public void commandLine_strictDeps() throws Exception { ProtoLangToolchainProvider toolchain = ProtoLangToolchainProvider.create( "--java_out=param1,param2:$(OUT)", /* pluginExecutable= */ null, /* runtime= */ mock(TransitiveInfoCollection.class), /* blacklistedProtos= */ NestedSetBuilder.emptySet(STABLE_ORDER)); SupportData supportData = SupportData.create( Predicates.alwaysFalse(), ImmutableList.of(artifact("//:dont-care", "source_file.proto")), NestedSetBuilder.create(STABLE_ORDER, artifact("//:dont-care", "import1.proto")), NestedSetBuilder.create( STABLE_ORDER, artifact("//:dont-care", "import1.proto"), artifact("//:dont-care", "import2.proto")), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* protoSourceRoot= */ "", /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* hasProtoSources= */ true, /* protosInExports= */ null, /* exportedProtoSourceRoots= */ null); CustomCommandLine cmdLine = createCommandLineFromToolchains( ImmutableList.of(new ToolchainInvocation("dontcare", toolchain, "foo.srcjar")), supportData.getDirectProtoSources(), supportData.getTransitiveImports(), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* directProtoSourceRoots= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* exportedProtoSourceRoots= */ null, supportData.getProtosInDirectDeps(), /* protosInExports= */ null, Label.parseAbsoluteUnchecked("//foo:bar"), /* allowServices= */ true, /* protocOpts= */ ImmutableList.of()); assertThat(cmdLine.arguments()) .containsExactly( "--java_out=param1,param2:foo.srcjar", "-Iimport1.proto=import1.proto", "-Iimport2.proto=import2.proto", "--direct_dependencies", "import1.proto", String.format(ProtoCompileActionBuilder.STRICT_DEPS_FLAG_TEMPLATE, "//foo:bar"), "source_file.proto") .inOrder(); } @Test public void otherParameters() throws Exception { SupportData supportData = SupportData.create( Predicates.alwaysFalse(), ImmutableList.of(), /* protosInDirectDeps= */ NestedSetBuilder.emptySet(STABLE_ORDER), NestedSetBuilder.emptySet(STABLE_ORDER), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* protoSourceRoot= */ "", /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* hasProtoSources= */ true, /* protosInExports= */ null, /* exportedProtoSourceRoots= */ null); CustomCommandLine cmdLine = createCommandLineFromToolchains( ImmutableList.of(), supportData.getDirectProtoSources(), supportData.getTransitiveImports(), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* directProtoSourceRoots= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* exportedProtoSourceRoots= */ null, supportData.getProtosInDirectDeps(), /* protosInExports= */ null, Label.parseAbsoluteUnchecked("//foo:bar"), /* allowServices= */ false, /* protocOpts= */ ImmutableList.of("--foo", "--bar")); assertThat(cmdLine.arguments()).containsAllOf("--disallow_services", "--foo", "--bar"); } @Test public void outReplacementAreLazilyEvaluated() throws Exception { final boolean[] hasBeenCalled = new boolean[1]; hasBeenCalled[0] = false; CharSequence outReplacement = new LazyString() { @Override public String toString() { hasBeenCalled[0] = true; return "mu"; } }; ProtoLangToolchainProvider toolchain = ProtoLangToolchainProvider.create( "--java_out=param1,param2:$(OUT)", /* pluginExecutable= */ null, /* runtime= */ mock(TransitiveInfoCollection.class), /* blacklistedProtos= */ NestedSetBuilder.emptySet(STABLE_ORDER)); SupportData supportData = SupportData.create( Predicates.alwaysFalse(), ImmutableList.of(), /* protosInDirectDeps= */ NestedSetBuilder.emptySet(STABLE_ORDER), NestedSetBuilder.emptySet(STABLE_ORDER), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* protoSourceRoot= */ "", /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* hasProtoSources= */ true, /* protosInExports= */ null, /* exportedProtoSourceRoots= */ null); CustomCommandLine cmdLine = createCommandLineFromToolchains( ImmutableList.of(new ToolchainInvocation("pluginName", toolchain, outReplacement)), supportData.getDirectProtoSources(), supportData.getTransitiveImports(), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* directProtoSourceRoots= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* exportedProtoSourceRoots= */ null, supportData.getProtosInDirectDeps(), /* protosInExports= */ null, Label.parseAbsoluteUnchecked("//foo:bar"), /* allowServices= */ true, /* protocOpts= */ ImmutableList.of()); assertThat(hasBeenCalled[0]).isFalse(); cmdLine.arguments(); assertThat(hasBeenCalled[0]).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 { SupportData supportData = SupportData.create( Predicates.alwaysFalse(), ImmutableList.of(), /* protosInDirectDeps= */ NestedSetBuilder.emptySet(STABLE_ORDER), NestedSetBuilder.emptySet(STABLE_ORDER), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* protoSourceRoot= */ "", /* directProtoSourceRoots= */ NestedSetBuilder.stableOrder().build(), /* hasProtoSources= */ true, /* protosInExports= */ null, /* exportedProtoSourceRoots= */ null); ProtoLangToolchainProvider toolchain1 = ProtoLangToolchainProvider.create( "dontcare", /* pluginExecutable= */ null, /* runtime= */ mock(TransitiveInfoCollection.class), /* blacklistedProtos= */ NestedSetBuilder.emptySet(STABLE_ORDER)); ProtoLangToolchainProvider toolchain2 = ProtoLangToolchainProvider.create( "dontcare", /* pluginExecutable= */ null, /* runtime= */ mock(TransitiveInfoCollection.class), /* blacklistedProtos= */ NestedSetBuilder.emptySet(STABLE_ORDER)); try { createCommandLineFromToolchains( ImmutableList.of( new ToolchainInvocation("pluginName", toolchain1, "outReplacement"), new ToolchainInvocation("pluginName", toolchain2, "outReplacement")), supportData.getDirectProtoSources(), supportData.getTransitiveImports(), /* transitiveProtoPathFlags= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* directProtoSourceRoots= */ NestedSetBuilder.emptySet(STABLE_ORDER), /* exportedProtoSourceRoots= */ null, supportData.getProtosInDirectDeps(), /* protosInExports= */ null, Label.parseAbsoluteUnchecked("//foo:bar"), /* allowServices= */ true, /* protocOpts= */ ImmutableList.of()); fail("Expected an exception"); } catch (IllegalStateException e) { assertThat(e) .hasMessageThat() .isEqualTo( "Invocation name pluginName appears more than once. " + "This could lead to incorrect proto-compiler behavior"); } } @Test public void testProtoCommandLineArgv() throws Exception { assertThat( protoArgv( null /* directDependencies */, ImmutableList.of(derivedArtifact("//:dont-care", "foo.proto")))) .containsExactly("-Ifoo.proto=out/foo.proto"); assertThat( protoArgv( ImmutableList.of() /* directDependencies */, ImmutableList.of(derivedArtifact("//:dont-care", "foo.proto")))) .containsExactly("-Ifoo.proto=out/foo.proto", "--direct_dependencies="); assertThat( protoArgv( ImmutableList.of( derivedArtifact("//:dont-care", "foo.proto")) /* directDependencies */, ImmutableList.of(derivedArtifact("//:dont-care", "foo.proto")))) .containsExactly("-Ifoo.proto=out/foo.proto", "--direct_dependencies", "foo.proto"); assertThat( protoArgv( ImmutableList.of( derivedArtifact("//:dont-care", "foo.proto"), derivedArtifact("//:dont-care", "bar.proto")) /* directDependencies */, ImmutableList.of(derivedArtifact("//:dont-care", "foo.proto")))) .containsExactly( "-Ifoo.proto=out/foo.proto", "--direct_dependencies", "foo.proto:bar.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( null /* protosInDirectoDependencies */, ImmutableList.of(artifact("@bla//foo:bar", "external/bla/foo/bar.proto")))) .containsExactly("-Ifoo/bar.proto=external/bla/foo/bar.proto"); } // TODO(b/34107586): Fix and enable test. @Ignore @Test public void directDependenciesOnExternalFiles() throws Exception { ImmutableList protos = ImmutableList.of(artifact("@bla//foo:bar", "external/bla/foo/bar.proto")); assertThat(protoArgv(protos, protos)).containsExactly("--direct_dependencies", "foo/bar.proto"); } private Artifact artifact(String ownerLabel, String path) { return new Artifact( root, root.getExecPath().getRelative(path), new LabelArtifactOwner(Label.parseAbsoluteUnchecked(ownerLabel))); } /** Creates a dummy artifact with the given path, that actually resides in /out/. */ private Artifact derivedArtifact(String ownerLabel, String path) { return new Artifact( derivedRoot, derivedRoot.getExecPath().getRelative(path), new LabelArtifactOwner(Label.parseAbsoluteUnchecked(ownerLabel))); } private static Iterable protoArgv( @Nullable Iterable protosInDirectDependencies, Iterable transitiveImports) { CustomCommandLine.Builder commandLine = CustomCommandLine.builder(); NestedSet protosInDirectDependenciesBuilder = protosInDirectDependencies != null ? NestedSetBuilder.wrap(STABLE_ORDER, protosInDirectDependencies) : null; NestedSet transitiveImportsNestedSet = NestedSetBuilder.wrap(STABLE_ORDER, transitiveImports); ProtoCompileActionBuilder.addIncludeMapArguments( commandLine, protosInDirectDependenciesBuilder, NestedSetBuilder.emptySet(STABLE_ORDER), transitiveImportsNestedSet); return commandLine.build().arguments(); } }