| // Copyright 2021 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.view.cpp; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.devtools.build.lib.actions.ActionLookupData; |
| import com.google.devtools.build.lib.actions.BuildFailedException; |
| import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; |
| import com.google.devtools.build.lib.events.EventKind; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.skyframe.ValueWithMetadata; |
| import com.google.testing.junit.testparameterinjector.TestParameter; |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; |
| import java.io.IOException; |
| import java.util.regex.Pattern; |
| import org.junit.After; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| /** Tests for CppCompileActionTemplate. */ |
| @RunWith(TestParameterInjector.class) |
| public class CppTemplateTest extends BuildIntegrationTestCase { |
| @Test |
| public void irrelevantFile(@TestParameter boolean keepGoing) throws Exception { |
| write( |
| "tree/tree.bzl", |
| "def _impl(ctx):", |
| " dir = ctx.actions.declare_directory('dir')", |
| " ctx.actions.run_shell(outputs = [dir], command = 'touch %s/file.cc %s/other.file' %" |
| + " (dir.path, dir.path))", |
| " return [DefaultInfo(files = depset([dir]))]", |
| "", |
| "tree = rule(implementation = _impl)"); |
| write( |
| "tree/BUILD", |
| "load(':tree.bzl', 'tree')", |
| "tree(name = 'lib')", |
| "cc_library(name = 'cc', srcs = [':lib'])"); |
| addOptions("--keep_going=" + keepGoing); |
| // In addition to testing the specific functionality of erroring out when there is an irrelevant |
| // file in the tree, this test also checks that error messages are properly cached in Skyframe. |
| // When messages are improperly not stored, the error message for --nokeep-going is suppressed |
| // after two runs because there are two actions (pic and non-pic), and whichever one |
| // fails first prevents the other one from being committed to the graph on the first run. Then |
| // the second run prints out the other's error message and only the third run shows no error. |
| for (int i = 0; i < (keepGoing ? 1 : 3); i++) { |
| assertThrows(BuildFailedException.class, () -> buildTarget("//tree:cc")); |
| events.assertContainsError( |
| Pattern.compile( |
| "tree/BUILD:[0-9]+:[0-9]+: Compiling all C\\+\\+ files in tree/dir failed: Artifact" |
| + " '.*/tree/dir/other.file' expanded from the directory artifact '.*/tree/dir'" |
| + " is neither header nor source file")); |
| events.clear(); |
| } |
| } |
| |
| private Path actionNamesBzl; |
| |
| @After |
| public void maybeDeleteModifiedActionNamesBzl() throws IOException { |
| if (actionNamesBzl != null) { |
| actionNamesBzl.delete(); |
| actionNamesBzl = null; |
| } |
| } |
| |
| @Test |
| public void badActionName() throws Exception { |
| write( |
| "tree/tree.bzl", |
| "def _impl(ctx):", |
| " dir = ctx.actions.declare_directory('dir')", |
| " ctx.actions.run_shell(outputs = [dir], command = 'touch %s/file.cc' % (dir.path))", |
| " return [DefaultInfo(files = depset([dir]))]", |
| "", |
| "tree = rule(implementation = _impl)"); |
| write( |
| "tree/BUILD", |
| "load(':tree.bzl', 'tree')", |
| "tree(name = 'lib')", |
| "cc_library(name = 'cc', srcs = [':lib'])"); |
| // Hack up the workspace to make *.cc files not associated to any C++ action name. Should never |
| // happen in practice. |
| actionNamesBzl = |
| getWorkspace().getRelative("third_party/bazel_rules/rules_cc/cc/action_names.bzl"); |
| String oldContents = new String(FileSystemUtils.readContentAsLatin1(actionNamesBzl)); |
| // Don't overwrite the user's source tree on a local test run when this is a symlink. |
| assertThat(actionNamesBzl.delete()).isTrue(); |
| String originalActionName = "CPP_COMPILE_ACTION_NAME = \"c++-compile\""; |
| assertThat(oldContents).contains(originalActionName); |
| FileSystemUtils.writeContentAsLatin1( |
| actionNamesBzl, |
| oldContents.replace(originalActionName, "CPP_COMPILE_ACTION_NAME = \"c++++++-compile\"")); |
| String writtenContents = new String(FileSystemUtils.readContentAsLatin1(actionNamesBzl)); |
| assertThat(writtenContents).contains("\"c++++++-compile\""); |
| assertThrows(BuildFailedException.class, () -> buildTarget("//tree:cc")); |
| events.assertContainsError( |
| Pattern.compile( |
| "tree/BUILD:[0-9]+:[0-9]+: Compiling all C\\+\\+ files in tree/dir failed: Expected" |
| + " action_config for 'c\\+\\+-compile' to be configured")); |
| } |
| |
| @Test |
| public void warningNotPersisted() throws Exception { |
| write( |
| "tree/tree.bzl", |
| "def _impl(ctx):", |
| " dir = ctx.actions.declare_directory('dir')", |
| " ctx.actions.run_shell(outputs = [dir], command = 'touch %s/file.cc' % (dir.path))", |
| " return [DefaultInfo(files = depset([dir]))]", |
| "", |
| "tree = rule(implementation = _impl)"); |
| write( |
| "tree/BUILD", |
| "load(':tree.bzl', 'tree')", |
| "tree(name = 'lib', deprecation = 'This is a warning')"); |
| write("cc/BUILD", "cc_library(name = 'cc', srcs = ['//tree:lib'])"); |
| buildTarget("//cc:cc"); |
| events.assertContainsEvent(EventKind.WARNING, "This is a warning"); |
| getSkyframeExecutor() |
| .getEvaluator() |
| .getDoneValues() |
| .forEach( |
| (k, v) -> { |
| if (k instanceof ActionLookupData) { |
| assertWithMessage("Node " + k + " warnings") |
| .that(ValueWithMetadata.getEvents(v).toList()) |
| .isEmpty(); |
| } |
| }); |
| |
| // Warning is shown on a no-op incremental build. |
| events.clear(); |
| buildTarget("//cc:cc"); |
| events.assertContainsEvent(EventKind.WARNING, "This is a warning"); |
| } |
| } |