// 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.pkgcache; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.cmdline.ResolvedTargets; import com.google.devtools.build.lib.cmdline.TargetParsingException; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase; import com.google.devtools.build.lib.vfs.Path; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * A test for {@link CompileOneDependencyTransformer}. */ @RunWith(JUnit4.class) public class CompileOneDependencyTransformerTest extends PackageLoadingTestCase { private static Set<Label> targetsToLabels(Iterable<Target> targets) { return AbstractTargetPatternEvaluatorTest.targetsToLabels(targets); } private TargetPatternEvaluator parser; private CompileOneDependencyTransformer transformer; @Before public final void createTransformer() throws Exception { parser = getPackageManager().newTargetPatternEvaluator(); transformer = new CompileOneDependencyTransformer(getPackageManager()); } private void writeSimpleExample() throws IOException { scratch.file("foo/BUILD", "cc_library(name = 'foo1', srcs = [ 'foo1.cc' ], hdrs = [ 'foo1.h' ])", "exports_files(['baz/bang'])"); scratch.file("foo/bar/BUILD", "cc_library(name = 'bar1', alwayslink = 1)", "cc_library(name = 'bar2')", "exports_files(['wiz/bang', 'wiz/all', 'baz', 'baz/bang', 'undeclared.h'])"); } private static Set<Label> labels(String... labelStrings) throws LabelSyntaxException { Set<Label> labels = new HashSet<>(); for (String labelString : labelStrings) { labels.add(Label.parseAbsolute(labelString)); } return labels; } private static ResolvedTargets<Target> parseTargetPatternList( TargetPatternEvaluator parser, Reporter reporter, List<String> targetPatterns, FilteringPolicy policy, boolean keepGoing) throws Exception { return parser.parseTargetPatternList(reporter, targetPatterns, policy, keepGoing); } private ResolvedTargets<Target> parseCompileOneDep(String... patterns) throws Exception { ResolvedTargets<Target> result = parseTargetPatternList(parser, reporter, Arrays.asList(patterns), FilteringPolicies.NO_FILTER, false); return transformer.transformCompileOneDependency(reporter, result); } private Set<Label> parseListCompileOneDep(String... patterns) throws Exception { return targetsToLabels(getFailFast(parseCompileOneDep(patterns))); } private Set<Label> parseListCompileOneDepRelative(String... patterns) throws TargetParsingException, IOException, InterruptedException { Path foo = scratch.dir("foo"); TargetPatternEvaluator fooOffsetParser = getPackageManager().newTargetPatternEvaluator(); fooOffsetParser.updateOffset(foo.relativeTo(rootDirectory)); ResolvedTargets<Target> result; try { result = fooOffsetParser.parseTargetPatternList( reporter, Arrays.asList(patterns), FilteringPolicies.NO_FILTER, false); } catch (InterruptedException e) { throw new RuntimeException(e); } result = transformer.transformCompileOneDependency(reporter, result); return targetsToLabels(getFailFast(result)); } private static Set<Target> getFailFast(ResolvedTargets<Target> result) { assertFalse(result.hasError()); return result.getTargets(); } @Test public void testCompileOneDep() throws Exception { writeSimpleExample(); assertThat(parseListCompileOneDep("foo/foo1.cc")) .containsExactlyElementsIn(labels("@//foo:foo1")); assertThat(parseListCompileOneDep("foo/foo1.h")) .containsExactlyElementsIn(labels("@//foo:foo1")); assertThat(parseListCompileOneDep("foo:foo1.cc")) .containsExactlyElementsIn(labels("@//foo:foo1")); assertThat(parseListCompileOneDep("//foo:foo1.cc")) .containsExactlyElementsIn(labels("@//foo:foo1")); assertThat(parseListCompileOneDepRelative("//foo:foo1.cc")) .containsExactlyElementsIn(labels("@//foo:foo1")); assertThat(parseListCompileOneDepRelative(":foo1.cc")) .containsExactlyElementsIn(labels("@//foo:foo1")); assertThat(parseListCompileOneDepRelative("foo1.cc")) .containsExactlyElementsIn(labels("@//foo:foo1")); } /** * Regression test for bug: * "--compile_one_dependency should report error for missing input". */ @Test public void testCompileOneDepOnMissingFile() throws Exception { writeSimpleExample(); try { parseCompileOneDep("//foo:missing.cc"); fail(); } catch (TargetParsingException e) { assertThat(e).hasMessage( "no such target '//foo:missing.cc': target 'missing.cc' not declared in package 'foo' " + "defined by /workspace/foo/BUILD"); } // Also, try a valid input file which has no dependent rules in its package. try { parseCompileOneDep("//foo:baz/bang"); fail(); } catch (TargetParsingException e) { assertThat(e).hasMessage("Couldn't find dependency on target '//foo:baz/bang'"); } // Try a header that is in a package but where no cc_library explicitly lists it. try { parseCompileOneDep("//foo/bar:undeclared.h"); fail(); } catch (TargetParsingException e) { assertThat(e).hasMessage("Couldn't find dependency on target '//foo/bar:undeclared.h'"); } } @Test public void testCompileOneDepOnNonSourceTarget() throws Exception { writeSimpleExample(); try { parseCompileOneDep("//foo:foo1"); fail(); } catch (TargetParsingException e) { assertThat(e).hasMessage("--compile_one_dependency target '//foo:foo1' must be a file"); } } @Test public void testCompileOneDepOnTwoTargets() throws Exception { scratch.file( "recursive/BUILD", "cc_library(name = 'x', srcs = ['foox.cc'])", "cc_library(name = 'y', srcs = ['fooy.cc'])"); assertThat(parseListCompileOneDep("//recursive:foox.cc", "//recursive:fooy.cc")) .containsExactlyElementsIn(labels("//recursive:x", "//recursive:y")); } /** * Regression test for bug: * "--compile_one_dependency should not crash in the presence of mutually recursive targets" */ @Test public void testCompileOneDepOnRecursiveTarget() throws Exception { scratch.file( "recursive/BUILD", "filegroup(name = 'x', srcs = ['foo.cc', ':y'])", "filegroup(name = 'y', srcs = [':x'])", "cc_library(name = 'foo', srcs = [':y'])"); assertThat(parseListCompileOneDep("//recursive:foo.cc")) .containsExactlyElementsIn(labels("//recursive:foo")); } @Test public void testCompileOneDepOnRecursiveNotFoundTarget() throws Exception { scratch.file("recursive/BUILD", "filegroup(name = 'x', srcs = [':y'])", "filegroup(name = 'y', srcs = [':x'])", "exports_files(['foo'])"); try { parseCompileOneDep("//recursive:foo"); fail(); } catch (TargetParsingException e) { assertThat(e).hasMessage("Couldn't find dependency on target '//recursive:foo'"); } } @Test public void testCompileOneDepOnDeepRecursiveTarget() throws Exception { scratch.file( "recursive/BUILD", "filegroup(name = 'x', srcs = ['foox.cc', ':y'])", "filegroup(name = 'y', srcs = ['fooy.cc', ':z'])", "filegroup(name = 'z', srcs = ['fooz.cc', ':x'])", "cc_library(name = 'cc', srcs = [':x'])"); Set<Label> result = parseListCompileOneDep("//recursive:foox.cc", "//recursive:fooy.cc", "//recursive:fooy.cc"); assertThat(result).containsExactlyElementsIn(labels("//recursive:cc")); } @Test public void testCompileOneDepOnCrossPackageRecursiveTarget() throws Exception { scratch.file( "recursive/BUILD", "filegroup(name = 'x', srcs = ['foo.cc', '//recursivetoo:x'])", "cc_library(name = 'cc', srcs = [':x'])"); scratch.file( "recursivetoo/BUILD", "filegroup(name = 'x', srcs = ['foo.cc', '//recursive:x'])", "cc_library(name = 'cc', srcs = [':x'])"); assertThat(parseListCompileOneDep("//recursive:foo.cc", "//recursivetoo:foo.cc")) .containsExactlyElementsIn(labels("//recursive:cc", "//recursivetoo:cc")); } /** * Tests that when multiple rules match the target, the one that appears first in the BUILD * file is chosen. */ @Test public void testRuleChoiceOrdering() throws Exception { scratch.file("a/BUILD", "cc_library(name = 'foo_lib', srcs = [ 'file.cc' ])", "cc_library(name = 'bar_lib', srcs = [ 'file.cc' ])"); scratch.file("b/BUILD", "cc_library(name = 'bar_lib', srcs = [ 'file.cc' ])", "cc_library(name = 'foo_lib', srcs = [ 'file.cc' ])"); assertThat(parseListCompileOneDep("a/file.cc")) .containsExactlyElementsIn(labels("//a:foo_lib")); assertThat(parseListCompileOneDep("b/file.cc")) .containsExactlyElementsIn(labels("//b:bar_lib")); } /** * Tests that when multiple rule match a target, language-specific rules take precedence. */ @Test public void testRuleChoiceLanguagePreferences() throws Exception { String srcs = "srcs = [ 'a.cc', 'a.c', 'a.h', 'a.java', 'a.py', 'a.txt' ])"; scratch.file("a/BUILD", "genrule(name = 'gen_rule', cmd = '', outs = [ 'out' ], " + srcs, "cc_library(name = 'cc_rule', " + srcs, "java_library(name = 'java_rule', " + srcs, "py_library(name = 'py_rule', " + srcs); assertThat(parseListCompileOneDep("a/a.cc")).containsExactlyElementsIn(labels("//a:cc_rule")); assertThat(parseListCompileOneDep("a/a.c")).containsExactlyElementsIn(labels("//a:cc_rule")); assertThat(parseListCompileOneDep("a/a.h")).containsExactlyElementsIn(labels("//a:cc_rule")); assertThat(parseListCompileOneDep("a/a.java")) .containsExactlyElementsIn(labels("//a:java_rule")); assertThat(parseListCompileOneDep("a/a.py")).containsExactlyElementsIn(labels("//a:py_rule")); assertThat(parseListCompileOneDep("a/a.txt")).containsExactlyElementsIn(labels("//a:gen_rule")); } @Test public void testGeneratedFile() throws Exception { scratch.file("a/BUILD", "genrule(name = 'gen_rule', cmd = '', outs = [ 'out.cc' ])", "cc_library(name = 'cc', srcs = ['out.cc'])"); assertThat(parseListCompileOneDep("a/out.cc")).containsExactlyElementsIn(labels("//a:cc")); } @Test public void testGeneratedFileDepOnGenerator() throws Exception { scratch.file( "a/BUILD", "genrule(name = 'gen_rule', cmd = '', outs = [ 'out.cc' ])", "cc_library(name = 'cc', srcs = [':gen_rule'])"); assertThat(parseListCompileOneDep("a/out.cc")).containsExactlyElementsIn(labels("//a:cc")); } @Test public void testHdrsFilegroup() throws Exception { scratch.file( "a/BUILD", "filegroup(name = 'headers', srcs = ['a.h'])", "cc_library(name = 'cc', hdrs = [':headers'], srcs = ['a.cc'])"); assertThat(parseListCompileOneDep("a/a.h")).containsExactlyElementsIn(labels("//a:cc")); } @Test public void testConfigurableSrcs() throws Exception { // TODO(djasper): We currently flatten the contents of configurable attributes, which might not // always do the right thing. In this situation it is actually good as compiling "foo_select" // at least has the chance to actually be a correct --compile_one_dependency choice for both // "b.cc" and "c.cc". However, if it also contained "a.cc" it might be better to still always // choose "foo_always". scratch.file( "a/BUILD", "config_setting(name = 'a', values = {'define': 'foo=a'})", "cc_library(name = 'foo_select', srcs = select({':a': ['b.cc'], ':b': ['c.cc']}))", "cc_library(name = 'foo_always', srcs = ['a.cc'])"); assertThat(parseListCompileOneDep("a/a.cc")) .containsExactlyElementsIn(labels("//a:foo_always")); assertThat(parseListCompileOneDep("a/b.cc")) .containsExactlyElementsIn(labels("//a:foo_select")); assertThat(parseListCompileOneDep("a/c.cc")) .containsExactlyElementsIn(labels("//a:foo_select")); } @Test public void testConfigurableCopts() throws Exception { // This configurable attribute doesn't preclude accurately knowing the srcs. scratch.file("a/BUILD", "config_setting(name = 'a', values = {'define': 'foo=a'})", "cc_library(name = 'foo_select', srcs = ['a.cc'],", " copts = select({':a': ['-DA'], ':b': ['-DB']}))", "cc_library(name = 'foo_always', srcs = ['a.cc'])"); assertThat(parseListCompileOneDep("a/a.cc")) .containsExactlyElementsIn(labels("//a:foo_select")); } @Test public void testFallBackToHeaderOnlyLibrary() throws Exception { scratch.file( "a/BUILD", "cc_library(name = 'h', hdrs = ['a.h'], features = ['parse_headers'])"); assertThat(parseListCompileOneDep("a/a.h")).containsExactlyElementsIn(labels("//a:h")); } }