blob: 51484ae9528527fc71b8a66398c03043d4fda930 [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.actions.util.ActionsTestUtil.getFirstArtifactEndingWith;
import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames;
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.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class BazelProtoLibraryTest extends BuildViewTestCase {
@Before
public void setUp() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler");
scratch.file("proto/BUILD", "licenses(['notice'])", "exports_files(['compiler'])");
}
@Test
public void createsDescriptorSets() throws Exception {
scratch.file(
"x/BUILD",
"proto_library(name='alias', deps = ['foo'])",
"proto_library(name='foo', srcs=['foo.proto'])",
"proto_library(name='alias_to_no_srcs', deps = ['no_srcs'])",
"proto_library(name='no_srcs')");
assertThat(getDescriptorOutput("//x:alias").getRootRelativePathString())
.isEqualTo("x/alias-descriptor-set.proto.bin");
assertThat(getDescriptorOutput("//x:foo").getRootRelativePathString())
.isEqualTo("x/foo-descriptor-set.proto.bin");
assertThat(getDescriptorOutput("//x:alias_to_no_srcs").getRootRelativePathString())
.isEqualTo("x/alias_to_no_srcs-descriptor-set.proto.bin");
assertThat(getDescriptorOutput("//x:no_srcs").getRootRelativePathString())
.isEqualTo("x/no_srcs-descriptor-set.proto.bin");
}
@Test
public void descriptorSets_ruleWithSrcsCallsProtoc() throws Exception {
scratch.file("x/BUILD", "proto_library(name='foo', srcs=['foo.proto'])");
Artifact file = getDescriptorOutput("//x:foo");
assertThat(getGeneratingSpawnAction(file).getRemainingArguments())
.containsAllOf(
"-Ix/foo.proto=x/foo.proto",
"--descriptor_set_out=" + file.getExecPathString(),
"x/foo.proto");
}
/** Asserts that we register a FileWriteAction with empty contents if there are no srcs. */
@Test
public void descriptorSets_ruleWithoutSrcsWritesEmptyFile() throws Exception {
scratch.file("x/BUILD", "proto_library(name='no_srcs')");
Action action = getDescriptorWriteAction("//x:no_srcs");
assertThat(action).isInstanceOf(FileWriteAction.class);
assertThat(((FileWriteAction) action).getFileContents()).isEmpty();
}
/**
* Asserts that the actions creating descriptor sets for rule R, take as input (=depend on) all of
* the descriptor sets of the transitive dependencies of R.
*
* <p>This is needed so that building R, that has a dependency R' which violates strict proto
* deps, would break.
*/
@Test
public void descriptorSetsDependOnChildren() throws Exception {
scratch.file(
"x/BUILD",
"proto_library(name='alias', deps = ['foo'])",
"proto_library(name='foo', srcs=['foo.proto'], deps = ['bar'])",
"proto_library(name='bar', srcs=['bar.proto'])",
"proto_library(name='alias_to_no_srcs', deps = ['no_srcs'])",
"proto_library(name='no_srcs')");
assertThat(getDepsDescriptorSets(getDescriptorOutput("//x:alias")))
.containsExactly("x/foo-descriptor-set.proto.bin", "x/bar-descriptor-set.proto.bin");
assertThat(getDepsDescriptorSets(getDescriptorOutput("//x:foo")))
.containsExactly("x/bar-descriptor-set.proto.bin");
assertThat(getDepsDescriptorSets(getDescriptorOutput("//x:bar"))).isEmpty();
assertThat(getDepsDescriptorSets(getDescriptorOutput("//x:alias_to_no_srcs")))
.containsExactly("x/no_srcs-descriptor-set.proto.bin");
assertThat(getDepsDescriptorSets(getDescriptorOutput("//x:no_srcs"))).isEmpty();
}
/**
* Returns all of the inputs of the action that generated 'descriptorSet', and which are
* themselves descriptor sets.
*/
private ImmutableList<String> getDepsDescriptorSets(Artifact descriptorSet) {
ImmutableList.Builder<String> result = ImmutableList.builder();
for (String input : prettyArtifactNames(getGeneratingAction(descriptorSet).getInputs())) {
if (input.endsWith("-descriptor-set.proto.bin")) {
result.add(input);
}
}
return result.build();
}
@Test
public void descriptorSetsAreExposedInProvider() throws Exception {
scratch.file(
"x/BUILD",
"proto_library(name='alias', deps = ['foo'])",
"proto_library(name='foo', srcs=['foo.proto'], deps = ['bar'])",
"proto_library(name='bar', srcs=['bar.proto'])",
"proto_library(name='alias_to_no_srcs', deps = ['no_srcs'])",
"proto_library(name='no_srcs')");
{
ProtoSourcesProvider provider =
getConfiguredTarget("//x:alias").getProvider(ProtoSourcesProvider.class);
assertThat(provider.directDescriptorSet().getRootRelativePathString())
.isEqualTo("x/alias-descriptor-set.proto.bin");
assertThat(prettyArtifactNames(provider.transitiveDescriptorSets()))
.containsExactly(
"x/alias-descriptor-set.proto.bin",
"x/foo-descriptor-set.proto.bin",
"x/bar-descriptor-set.proto.bin");
}
{
ProtoSourcesProvider provider =
getConfiguredTarget("//x:foo").getProvider(ProtoSourcesProvider.class);
assertThat(provider.directDescriptorSet().getRootRelativePathString())
.isEqualTo("x/foo-descriptor-set.proto.bin");
assertThat(prettyArtifactNames(provider.transitiveDescriptorSets()))
.containsExactly("x/foo-descriptor-set.proto.bin", "x/bar-descriptor-set.proto.bin");
}
{
ProtoSourcesProvider provider =
getConfiguredTarget("//x:bar").getProvider(ProtoSourcesProvider.class);
assertThat(provider.directDescriptorSet().getRootRelativePathString())
.isEqualTo("x/bar-descriptor-set.proto.bin");
assertThat(prettyArtifactNames(provider.transitiveDescriptorSets()))
.containsExactly("x/bar-descriptor-set.proto.bin");
}
{
ProtoSourcesProvider provider =
getConfiguredTarget("//x:alias_to_no_srcs").getProvider(ProtoSourcesProvider.class);
assertThat(provider.directDescriptorSet().getRootRelativePathString())
.isEqualTo("x/alias_to_no_srcs-descriptor-set.proto.bin");
assertThat(prettyArtifactNames(provider.transitiveDescriptorSets()))
.containsExactly(
"x/alias_to_no_srcs-descriptor-set.proto.bin", "x/no_srcs-descriptor-set.proto.bin");
}
{
ProtoSourcesProvider provider =
getConfiguredTarget("//x:no_srcs").getProvider(ProtoSourcesProvider.class);
assertThat(provider.directDescriptorSet().getRootRelativePathString())
.isEqualTo("x/no_srcs-descriptor-set.proto.bin");
assertThat(prettyArtifactNames(provider.transitiveDescriptorSets()))
.containsExactly("x/no_srcs-descriptor-set.proto.bin");
}
}
@Test
public void testDescriptorSetOutput_strictDeps() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler", "--strict_proto_deps=error");
scratch.file(
"x/BUILD",
"proto_library(name='nodeps', srcs=['nodeps.proto'])",
"proto_library(name='withdeps', srcs=['withdeps.proto'], deps=[':dep1', ':dep2'])",
"proto_library(name='depends_on_alias', srcs=['depends_on_alias.proto'], deps=[':alias'])",
"proto_library(name='alias', deps=[':dep1', ':dep2'])",
"proto_library(name='dep1', srcs=['dep1.proto'])",
"proto_library(name='dep2', srcs=['dep2.proto'])");
assertThat(getGeneratingSpawnAction(getDescriptorOutput("//x:nodeps")).getRemainingArguments())
.containsAllOf("--direct_dependencies", "x/nodeps.proto")
.inOrder();
assertThat(
getGeneratingSpawnAction(getDescriptorOutput("//x:withdeps")).getRemainingArguments())
.containsAllOf("--direct_dependencies", "x/dep1.proto:x/dep2.proto:x/withdeps.proto")
.inOrder();
assertThat(
getGeneratingSpawnAction(getDescriptorOutput("//x:depends_on_alias"))
.getRemainingArguments())
.containsAllOf(
"--direct_dependencies", "x/dep1.proto:x/dep2.proto:x/depends_on_alias.proto")
.inOrder();
}
/**
* When building a proto_library with multiple srcs (say foo.proto and bar.proto), we should allow
* foo.proto to import bar.proto without tripping strict-deps checking. This means that
* --direct_dependencies should list the srcs.
*/
@Test
public void testDescriptorSetOutput_strict_deps_multipleSrcs() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler", "--strict_proto_deps=error");
ConfiguredTarget target =
scratchConfiguredTarget(
"x", "foo", "proto_library(name='foo', srcs=['foo.proto', 'bar.proto'])");
Artifact file = getFirstArtifactEndingWith(getFilesToBuild(target), ".proto.bin");
assertThat(file.getRootRelativePathString()).isEqualTo("x/foo-descriptor-set.proto.bin");
assertThat(getGeneratingSpawnAction(file).getRemainingArguments())
.containsAllOf("--direct_dependencies", "x/foo.proto:x/bar.proto")
.inOrder();
}
@Test
public void testDescriptorSetOutput_strictDeps_disabled() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler", "--strict_proto_deps=off");
scratch.file("x/BUILD", "proto_library(name='foo', srcs=['foo.proto'])");
for (String arg :
getGeneratingSpawnAction(getDescriptorOutput("//x:foo")).getRemainingArguments()) {
assertThat(arg).doesNotContain("--direct_dependencies=");
}
}
@Test
public void testProtoSourceRootWithoutDeps() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler");
scratch.file(
"x/foo/BUILD",
"proto_library(",
" name = 'nodeps',",
" srcs = ['foo/nodeps.proto'],",
" proto_source_root = 'x/foo',",
")"
);
ConfiguredTarget protoTarget = getConfiguredTarget("//x/foo:nodeps");
ProtoSourcesProvider sourcesProvider = protoTarget.getProvider(ProtoSourcesProvider.class);
assertThat(sourcesProvider.getTransitiveProtoPathFlags()).containsExactly("x/foo");
SupportData supportData =
protoTarget.getProvider(ProtoSupportDataProvider.class).getSupportData();
assertThat(supportData.getTransitiveProtoPathFlags()).containsExactly("x/foo");
assertThat(getGeneratingSpawnAction(getDescriptorOutput("//x/foo:nodeps"))
.getRemainingArguments())
.contains("--proto_path=x/foo");
}
@Test
public void testProtoSourceRootWithoutDeps_notPackageName() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler");
scratch.file(
"x/foo/BUILD",
"proto_library(",
" name = 'nodeps',",
" srcs = ['foo/nodeps.proto'],",
" proto_source_root = 'something/else',",
")"
);
try {
getConfiguredTarget("//x/foo:nodeps");
} catch (AssertionError error) {
assertThat(error)
.hasMessageThat()
.contains("proto_source_root must be the same as the package name (x/foo)");
return;
}
throw new Exception("Target should have failed building.");
}
@Test
public void testProtoSourceRootWithDepsDuplicate() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler");
scratch.file(
"x/foo/BUILD",
"proto_library(",
" name = 'withdeps',",
" srcs = ['foo/withdeps.proto'],",
" proto_source_root = 'x/foo',",
" deps = [':dep'],",
")",
"proto_library(",
" name = 'dep',",
" srcs = ['foo/dep.proto'],",
" proto_source_root = 'x/foo',",
")"
);
ConfiguredTarget protoTarget = getConfiguredTarget("//x/foo:withdeps");
ProtoSourcesProvider sourcesProvider = protoTarget.getProvider(ProtoSourcesProvider.class);
assertThat(sourcesProvider.getTransitiveProtoPathFlags()).containsExactly("x/foo");
SupportData supportData =
protoTarget.getProvider(ProtoSupportDataProvider.class).getSupportData();
assertThat(supportData.getTransitiveProtoPathFlags()).containsExactly("x/foo");
assertThat(getGeneratingSpawnAction(getDescriptorOutput("//x/foo:withdeps"))
.getRemainingArguments())
.contains("--proto_path=x/foo");
}
@Test
public void testProtoSourceRootWithDeps() throws Exception {
useConfiguration("--proto_compiler=//proto:compiler");
scratch.file(
"x/foo/BUILD",
"proto_library(",
" name = 'withdeps',",
" srcs = ['foo/withdeps.proto'],",
" proto_source_root = 'x/foo',",
" deps = ['//x/bar:dep', ':dep'],",
")",
"proto_library(",
" name = 'dep',",
" srcs = ['foo/dep.proto'],",
")"
);
scratch.file(
"x/bar/BUILD",
"proto_library(",
" name = 'dep',",
" srcs = ['foo/dep.proto'],",
" proto_source_root = 'x/bar',",
")"
);
ConfiguredTarget protoTarget = getConfiguredTarget("//x/foo:withdeps");
ProtoSourcesProvider sourcesProvider = protoTarget.getProvider(ProtoSourcesProvider.class);
assertThat(sourcesProvider.getTransitiveProtoPathFlags())
.containsExactly("x/foo", "x/bar");
SupportData supportData =
protoTarget.getProvider(ProtoSupportDataProvider.class).getSupportData();
assertThat(supportData.getTransitiveProtoPathFlags())
.containsExactly("x/foo", "x/bar");
assertThat(getGeneratingSpawnAction(getDescriptorOutput("//x/foo:withdeps"))
.getRemainingArguments())
.containsAllOf("--proto_path=x/foo", "--proto_path=x/bar");
}
private Artifact getDescriptorOutput(String label) throws Exception {
return getFirstArtifactEndingWith(getFilesToBuild(getConfiguredTarget(label)), ".proto.bin");
}
private Action getDescriptorWriteAction(String label) throws Exception {
return getGeneratingAction(getDescriptorOutput(label));
}
}