blob: e194641c0b758da49905b3c0aaa836c716470cc7 [file] [log] [blame]
// Copyright 2022 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.buildtool;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.TestConstants.WORKSPACE_NAME;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.actions.BuildFailedException;
import com.google.devtools.build.lib.analysis.AnalysisPhaseCompleteEvent;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase;
import com.google.devtools.build.lib.skyframe.SkymeldModule;
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelEntityAnalysisConcludedEvent;
import com.google.devtools.build.lib.util.io.RecordingOutErr;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.skyframe.NodeEntry.LifecycleState;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Integration tests for project Skymeld: interleaving Skyframe's analysis and execution phases. */
@RunWith(TestParameterInjector.class)
public class SkymeldBuildIntegrationTest extends BuildIntegrationTestCase {
private EventsSubscriber eventsSubscriber;
@Before
public void setUp() {
addOptions("--experimental_merged_skyframe_analysis_execution");
this.eventsSubscriber = new EventsSubscriber();
runtimeWrapper.registerSubscriber(eventsSubscriber);
}
/** A simple rule that has srcs, deps and writes these attributes to its output. */
private void writeMyRuleBzl() throws IOException {
write(
"foo/my_rule.bzl",
"""
def _path(file):
return file.path
def _impl(ctx):
inputs = depset(
ctx.files.srcs,
transitive = [dep[DefaultInfo].files for dep in ctx.attr.deps],
)
output = ctx.actions.declare_file(ctx.attr.name + ".out")
command = "echo $@ > %s" % (output.path)
args = ctx.actions.args()
args.add_all(inputs, map_each = _path)
ctx.actions.run_shell(
inputs = inputs,
outputs = [output],
command = command,
arguments = [args],
)
return DefaultInfo(files = depset([output]))
my_rule = rule(
implementation = _impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
"deps": attr.label_list(providers = ["DefaultInfo"]),
},
)
""");
}
private void writeAnalysisFailureAspectBzl() throws IOException {
write(
"foo/aspect.bzl",
"""
def _aspect_impl(target, ctx):
malformed
analysis_err_aspect = aspect(implementation = _aspect_impl)
""");
}
private void writeExecutionFailureAspectBzl() throws IOException {
write(
"foo/aspect.bzl",
"""
def _aspect_impl(target, ctx):
output = ctx.actions.declare_file("aspect_output")
ctx.actions.run_shell(
outputs = [output],
command = "false",
)
return [OutputGroupInfo(
files = depset([output]),
)]
execution_err_aspect = aspect(implementation = _aspect_impl)
""");
}
private void writeEnvironmentRules(String... defaults) throws Exception {
StringBuilder defaultsBuilder = new StringBuilder();
for (String defaultEnv : defaults) {
defaultsBuilder.append("'").append(defaultEnv).append("', ");
}
write(
"buildenv/BUILD",
"environment_group(",
" name = 'group',",
" environments = [':one', ':two'],",
" defaults = [" + defaultsBuilder + "])",
"environment(name = 'one')",
"environment(name = 'two')");
}
@CanIgnoreReturnValue
private Path assertSingleOutputBuilt(String target) throws Exception {
Path singleOutput = Iterables.getOnlyElement(getArtifacts(target)).getPath();
assertThat(singleOutput.isFile()).isTrue();
return singleOutput;
}
@Test
public void nobuild_warning() throws Exception {
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
addOptions("--nobuild");
RecordingOutErr recordedOutput = divertInfoLogToOutErr();
BuildResult result = buildTarget("//foo:foo");
assertThat(result.getSuccess()).isTrue();
assertThat(recordedOutput.errAsLatin1())
.containsMatch(
"--experimental_merged_skyframe_analysis_execution is incompatible with --nobuild"
+ " and will be ignored");
}
@Test
public void multiTargetBuild_success() throws Exception {
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "bar",
srcs = ["bar.in"],
)
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
write("foo/bar.in");
BuildResult result = buildTarget("//foo:foo", "//foo:bar");
assertThat(result.getSuccess()).isTrue();
assertSingleOutputBuilt("//foo:foo");
assertSingleOutputBuilt("//foo:bar");
assertThat(getLabelsOfAnalyzedTargets()).containsExactly("//foo:foo", "//foo:bar");
assertThat(getLabelsOfBuiltTargets()).containsExactly("//foo:foo", "//foo:bar");
assertThat(eventsSubscriber.getTopLevelEntityAnalysisConcludedEvents()).hasSize(2);
assertSingleAnalysisPhaseCompleteEventWithLabels("//foo:foo", "//foo:bar");
assertThat(directories.getOutputPath(WORKSPACE_NAME).getRelative("build-info.txt").isFile())
.isTrue();
assertThat(
directories.getOutputPath(WORKSPACE_NAME).getRelative("build-changelist.txt").isFile())
.isTrue();
}
@Test
public void multiTargetNullIncrementalBuild_success() throws Exception {
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "bar",
srcs = ["bar.in"],
)
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
write("foo/bar.in");
// First build, ignored.
buildTarget("//foo:foo", "//foo:bar");
BuildResult result = buildTarget("//foo:foo", "//foo:bar");
assertThat(result.getSuccess()).isTrue();
assertSingleOutputBuilt("//foo:foo");
assertSingleOutputBuilt("//foo:bar");
assertThat(directories.getOutputPath(WORKSPACE_NAME).getRelative("build-info.txt").isFile())
.isTrue();
assertThat(
directories.getOutputPath(WORKSPACE_NAME).getRelative("build-changelist.txt").isFile())
.isTrue();
}
@Test
public void sequentialBuilds_verifyNodesAreDone(@TestParameter boolean mergedAnalysisExecution)
throws Exception {
addOptions("--experimental_merged_skyframe_analysis_execution=" + mergedAnalysisExecution);
write("hello/x.txt", "x");
write(
"hello/BUILD",
"""
genrule(
name = "target",
srcs = ["x.txt"],
outs = ["out"],
cmd = "cat $< > $@",
)
genrule(
name = "target2",
srcs = ["x.txt"],
outs = ["out2"],
cmd = "cat $< > $@",
)
""");
buildTarget("//hello:target");
assertThat(getSkyframeExecutor().getEvaluator().getDoneValues())
.containsExactlyEntriesIn(getSkyframeExecutor().getEvaluator().getValues());
buildTarget("//hello:target2");
if (mergedAnalysisExecution) {
// BuildDriverKey of the previous build with be marked dirty from its child BUILD_ID dep.
// However, only the new BuildDriverKey will be evaluated and marked done.
SkyKey dirtyKey =
Iterables.getOnlyElement(
Sets.difference(
getSkyframeExecutor().getEvaluator().getValues().keySet(),
getSkyframeExecutor().getEvaluator().getDoneValues().keySet()));
assertThat(dirtyKey.getCanonicalName())
.contains(
"BUILD_DRIVER:BuildDriverKey of ActionLookupKey:"
+ " ConfiguredTargetKey{label=//hello:target");
assertThat(
getSkyframeExecutor()
.getEvaluator()
.getInMemoryGraph()
.getIfPresent(dirtyKey)
.getLifecycleState())
.isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
} else {
// This doesn't happen for non-Skymeld builds.
assertThat(getSkyframeExecutor().getEvaluator().getDoneValues())
.containsExactlyEntriesIn(getSkyframeExecutor().getEvaluator().getValues());
}
}
@Test
public void aspectAnalysisFailure_consistentWithNonSkymeld(
@TestParameter boolean keepGoing, @TestParameter boolean mergedAnalysisExecution)
throws Exception {
addOptions("--keep_going=" + keepGoing);
addOptions("--experimental_merged_skyframe_analysis_execution=" + mergedAnalysisExecution);
writeMyRuleBzl();
writeAnalysisFailureAspectBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
addOptions("--aspects=//foo:aspect.bzl%analysis_err_aspect", "--output_groups=files");
if (keepGoing) {
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo"));
} else {
assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:foo"));
}
events.assertContainsError("compilation of module 'foo/aspect.bzl' failed");
}
@Test
public void aspectExecutionFailure_consistentWithNonSkymeld(
@TestParameter boolean keepGoing, @TestParameter boolean mergedAnalysisExecution)
throws Exception {
addOptions("--keep_going=" + keepGoing);
addOptions("--experimental_merged_skyframe_analysis_execution=" + mergedAnalysisExecution);
writeMyRuleBzl();
writeExecutionFailureAspectBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
addOptions("--aspects=//foo:aspect.bzl%execution_err_aspect", "--output_groups=files");
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo"));
events.assertContainsError(
"Action foo/aspect_output failed: (Exit 1): bash failed: error executing Action command");
}
@Test
public void targetExecutionFailure_consistentWithNonSkymeld(
@TestParameter boolean keepGoing, @TestParameter boolean mergedAnalysisExecution)
throws Exception {
addOptions("--keep_going=" + keepGoing);
addOptions("--experimental_merged_skyframe_analysis_execution=" + mergedAnalysisExecution);
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "execution_failure",
srcs = ["missing"],
)
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
assertThrows(
BuildFailedException.class, () -> buildTarget("//foo:foo", "//foo:execution_failure"));
if (keepGoing) {
assertSingleOutputBuilt("//foo:foo");
}
events.assertContainsError(
"Action foo/execution_failure.out failed: missing input file '//foo:missing'");
}
@Test
public void targetAnalysisFailure_consistentWithNonSkymeld(
@TestParameter boolean keepGoing, @TestParameter boolean mergedAnalysisExecution)
throws Exception {
addOptions("--keep_going=" + keepGoing);
addOptions("--experimental_merged_skyframe_analysis_execution=" + mergedAnalysisExecution);
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "analysis_failure",
srcs = ["foo.in"],
deps = [":missing"],
)
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
if (keepGoing) {
assertThrows(
BuildFailedException.class, () -> buildTarget("//foo:foo", "//foo:analysis_failure"));
assertSingleOutputBuilt("//foo:foo");
} else {
assertThrows(
ViewCreationFailedException.class,
() -> buildTarget("//foo:foo", "//foo:analysis_failure"));
}
events.assertContainsError("rule '//foo:missing' does not exist");
}
// Regression test for https://github.com/bazelbuild/bazel/issues/20443
@Test
public void testKeepGoingWarningContainsDetails() throws Exception {
addOptions("--keep_going");
write(
"foo/BUILD",
"""
constraint_setting(name = "incompatible_setting")
constraint_value(
name = "incompatible",
constraint_setting = ":incompatible_setting",
visibility = ["//visibility:public"],
)
cc_library(
name = "foo",
srcs = ["foo.cc"],
target_compatible_with = ["//foo:incompatible"],
)
""");
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo"));
events.assertContainsWarning(
"errors encountered while analyzing target '//foo:foo', it will not be built.");
// The details.
events.assertContainsWarning("Dependency chain:");
}
@Test
public void analysisAndExecutionFailure_keepGoing_bothReported() throws Exception {
addOptions("--keep_going");
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "execution_failure",
srcs = ["missing"],
)
my_rule(
name = "analysis_failure",
srcs = ["foo.in"],
deps = [":missing"],
)
""");
write("foo/foo.in");
assertThrows(
BuildFailedException.class,
() -> buildTarget("//foo:analysis_failure", "//foo:execution_failure"));
events.assertContainsError(
"Action foo/execution_failure.out failed: missing input file '//foo:missing'");
events.assertContainsError("rule '//foo:missing' does not exist");
assertThat(getLabelsOfAnalyzedTargets()).contains("//foo:execution_failure");
assertThat(getLabelsOfBuiltTargets()).isEmpty();
}
@Test
public void symlinkPlantedLocalAction_success() throws Exception {
addOptions("--spawn_strategy=standalone");
write(
"foo/BUILD",
"""
genrule(
name = "foo",
srcs = ["foo.in"],
outs = ["foo.out"],
cmd = "cp $< $@",
)
""");
write("foo/foo.in");
BuildResult result = buildTarget("//foo:foo");
assertThat(result.getSuccess()).isTrue();
assertSingleOutputBuilt("//foo:foo");
}
@Test
public void symlinksPlanted() throws Exception {
Path execroot = directories.getExecRoot(directories.getWorkspace().getBaseName());
writeMyRuleBzl();
Path fooDir =
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""")
.getParentDirectory();
write("foo/foo.in");
Path unusedDir = write("unused/dummy").getParentDirectory();
// Before the build: no symlink.
assertThat(execroot.getRelative("foo").exists()).isFalse();
buildTarget("//foo:foo");
// After the build: symlinks to the source directory, even unused packages.
assertThat(execroot.getRelative("foo").resolveSymbolicLinks()).isEqualTo(fooDir);
assertThat(execroot.getRelative("unused").resolveSymbolicLinks()).isEqualTo(unusedDir);
}
@Test
public void symlinksPlantedExceptProductNamePrefixAndIgnoredPaths() throws Exception {
String productName = getRuntime().getProductName();
Path execroot = directories.getExecRoot(directories.getWorkspace().getBaseName());
writeMyRuleBzl();
Path fooDir =
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""")
.getParentDirectory();
write("foo/foo.in");
Path unusedDir = write("unused/dummy").getParentDirectory();
write(".bazelignore", "ignored");
write("ignored/dummy");
write(productName + "-dir/dummy");
// Before the build: no symlink.
assertThat(execroot.getRelative("foo").exists()).isFalse();
buildTarget("//foo:foo");
// After the build: symlinks to the source directory, even unused packages, except for those
// in the .bazelignore file and those with the bazel- prefix.
assertThat(execroot.getRelative("foo").resolveSymbolicLinks()).isEqualTo(fooDir);
assertThat(execroot.getRelative("unused").resolveSymbolicLinks()).isEqualTo(unusedDir);
assertThat(execroot.getRelative("ignored").exists()).isFalse();
assertThat(execroot.getRelative(productName + "-dir").exists()).isFalse();
}
@Test
public void symlinksReplantedEachBuild() throws Exception {
Path execroot = directories.getExecRoot(directories.getWorkspace().getBaseName());
writeMyRuleBzl();
Path fooDir =
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""")
.getParentDirectory();
write("foo/foo.in");
Path unusedDir = write("unused/dummy").getParentDirectory();
buildTarget("//foo:foo");
// After the 1st build: symlinks to the source directory, even unused packages.
assertThat(execroot.getRelative("foo").resolveSymbolicLinks()).isEqualTo(fooDir);
assertThat(execroot.getRelative("unused").resolveSymbolicLinks()).isEqualTo(unusedDir);
unusedDir.deleteTree();
buildTarget("//foo:foo");
// After the 2nd build: symlink to unusedDir is gone, since the package itself was deleted.
assertThat(execroot.getRelative("foo").resolveSymbolicLinks()).isEqualTo(fooDir);
assertThat(execroot.getRelative("unused").exists()).isFalse();
}
@Test
public void targetAnalysisFailure_skymeld_correctAnalysisEvents(@TestParameter boolean keepGoing)
throws Exception {
addOptions("--keep_going=" + keepGoing);
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "analysis_failure",
srcs = ["foo.in"],
deps = [":missing"],
)
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
if (keepGoing) {
assertThrows(
BuildFailedException.class, () -> buildTarget("//foo:foo", "//foo:analysis_failure"));
assertThat(eventsSubscriber.getTopLevelEntityAnalysisConcludedEvents()).hasSize(2);
assertSingleAnalysisPhaseCompleteEventWithLabels("//foo:foo");
} else {
assertThrows(
ViewCreationFailedException.class,
() -> buildTarget("//foo:foo", "//foo:analysis_failure"));
assertThat(eventsSubscriber.getAnalysisPhaseCompleteEvents()).isEmpty();
}
}
@Test
public void aspectAnalysisFailure_skymeld_correctAnalysisEvents(@TestParameter boolean keepGoing)
throws Exception {
addOptions("--keep_going=" + keepGoing);
writeMyRuleBzl();
writeAnalysisFailureAspectBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
addOptions("--aspects=//foo:aspect.bzl%analysis_err_aspect", "--output_groups=files");
if (keepGoing) {
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo"));
assertThat(eventsSubscriber.getTopLevelEntityAnalysisConcludedEvents()).hasSize(2);
assertSingleAnalysisPhaseCompleteEventWithLabels("//foo:foo");
} else {
assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:foo"));
assertThat(eventsSubscriber.getAnalysisPhaseCompleteEvents()).isEmpty();
}
events.assertContainsError("compilation of module 'foo/aspect.bzl' failed");
}
@Test
public void targetSkipped_skymeld_correctAnalysisEvents(@TestParameter boolean keepGoing)
throws Exception {
writeEnvironmentRules();
addOptions("--keep_going=" + keepGoing);
write(
"foo/BUILD",
"""
filegroup(
name = "good_bar",
srcs = ["bar.sh"],
compatible_with = ["//buildenv:one"],
)
filegroup(
name = "bad_bar",
srcs = ["bar.sh"],
compatible_with = ["//buildenv:two"],
)
""");
write("foo/bar.sh");
addOptions("--target_environment=//buildenv:one");
if (keepGoing) {
assertThrows(
BuildFailedException.class, () -> buildTarget("//foo:good_bar", "//foo:bad_bar"));
assertThat(eventsSubscriber.getTopLevelEntityAnalysisConcludedEvents()).hasSize(2);
assertThat(eventsSubscriber.getAnalysisPhaseCompleteEvents()).hasSize(1);
AnalysisPhaseCompleteEvent analysisPhaseCompleteEvent =
Iterables.getOnlyElement(eventsSubscriber.getAnalysisPhaseCompleteEvents());
assertThat(analysisPhaseCompleteEvent.getTimeInMs()).isGreaterThan(0);
assertThat(getLabelsOfAnalyzedTargets(analysisPhaseCompleteEvent))
.containsExactly("//foo:good_bar", "//foo:bad_bar");
} else {
assertThrows(
ViewCreationFailedException.class, () -> buildTarget("//foo:good_bar", "//foo:bad_bar"));
assertThat(eventsSubscriber.getAnalysisPhaseCompleteEvents()).isEmpty();
}
}
@Test
public void targetWithNoConfiguration_success() throws Exception {
write("foo/BUILD", "exports_files(['bar.txt'])");
write("foo/bar.txt", "This is just a test file to pretend to build.");
BuildResult result = buildTarget("//foo:bar.txt");
assertThat(result.getSuccess()).isTrue();
}
@Test
public void explain_ignoreSkymeldWithWarning() throws Exception {
addOptions("--explain=/dev/null");
write("foo/BUILD", "genrule(name = 'foo', outs = ['foo.out'], cmd = 'touch $@')");
RecordingOutErr recordedOutput = divertInfoLogToOutErr();
BuildResult buildResult = buildTarget("//foo");
assertThat(buildResult.getSuccess()).isTrue();
assertThat(recordedOutput.errAsLatin1())
.containsMatch(
"--experimental_merged_skyframe_analysis_execution is incompatible with --explain"
+ " and will be ignored.");
}
@Test
public void multiplePackagePath_ignoreSkymeldWithWarning() throws Exception {
write("foo/BUILD", "genrule(name = 'foo', outs = ['foo.out'], cmd = 'touch $@')");
write("otherroot/bar/BUILD", "genrule(name = 'bar', outs = ['bar.out'], cmd = 'touch $@')");
addOptions("--package_path=%workspace%:otherroot");
RecordingOutErr recordedOutput = divertInfoLogToOutErr();
BuildResult buildResult = buildTarget("//foo", "//bar");
assertThat(buildResult.getSuccess()).isTrue();
assertThat(recordedOutput.errAsLatin1())
.containsMatch(
"--experimental_merged_skyframe_analysis_execution is incompatible with multiple"
+ " --package_path.*and its value will be ignored.");
}
// Regression test for b/245919888.
@Test
public void outputFileRemoved_regeneratedWithIncrementalBuild() throws Exception {
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
BuildResult result = buildTarget("//foo:foo");
assertThat(result.getSuccess()).isTrue();
Path fooOut = assertSingleOutputBuilt("//foo:foo");
fooOut.delete();
BuildResult incrementalBuild = buildTarget("//foo:foo");
assertThat(incrementalBuild.getSuccess()).isTrue();
assertSingleOutputBuilt("//foo:foo");
}
// Regression test for b/245922900.
@Test
public void executionFailure_discardAnalysisCache_doesNotCrash() throws Exception {
addOptions("--experimental_merged_skyframe_analysis_execution", "--discard_analysis_cache");
writeExecutionFailureAspectBzl();
write(
"foo/BUILD",
"""
cc_library(
name = "foo",
srcs = ["foo.cc"],
deps = [":bar"],
)
cc_library(
name = "bar",
srcs = ["bar.cc"],
)
""");
write("foo/foo.cc");
write("foo/bar.cc");
addOptions("--aspects=//foo:aspect.bzl%execution_err_aspect", "--output_groups=files");
// Verify that the build did not crash.
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo"));
events.assertContainsError(
"Action foo/aspect_output failed: (Exit 1): bash failed: error executing Action command");
}
@Test
public void targetCycle_doesNotCrash() throws Exception {
write(
"a/BUILD",
"""
alias(
name = "a",
actual = ":b",
)
alias(
name = "b",
actual = ":c",
)
alias(
name = "c",
actual = ":a",
)
filegroup(
name = "d",
srcs = [":c"],
)
""");
assertThrows(ViewCreationFailedException.class, () -> buildTarget("//a:d"));
events.assertContainsError("cycle in dependency graph");
}
@Test
public void analysisOverlapPercentageSanityCheck_success() throws Exception {
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "bar",
srcs = ["bar.in"],
)
my_rule(
name = "foo",
srcs = ["foo.in"],
)
""");
write("foo/foo.in");
write("foo/bar.in");
addOptions("--experimental_skymeld_analysis_overlap_percentage=5");
BuildResult result = buildTarget("//foo:foo", "//foo:bar");
assertThat(result.getSuccess()).isTrue();
assertSingleOutputBuilt("//foo:foo");
assertSingleOutputBuilt("//foo:bar");
assertThat(getLabelsOfAnalyzedTargets()).containsExactly("//foo:foo", "//foo:bar");
assertThat(getLabelsOfBuiltTargets()).containsExactly("//foo:foo", "//foo:bar");
assertThat(eventsSubscriber.getTopLevelEntityAnalysisConcludedEvents()).hasSize(2);
assertSingleAnalysisPhaseCompleteEventWithLabels("//foo:foo", "//foo:bar");
}
// Regression test for b/277783687.
@Test
public void targetAnalysisFailureNullBuild_correctErrorsPropagated(
@TestParameter boolean keepGoing) throws Exception {
addOptions("--keep_going=" + keepGoing);
writeMyRuleBzl();
write(
"foo/BUILD",
"""
load("//foo:my_rule.bzl", "my_rule")
my_rule(
name = "analysis_failure",
srcs = ["foo.in"],
deps = [":missing"],
)
""");
write("foo/foo.in");
if (keepGoing) {
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:analysis_failure"));
} else {
assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:analysis_failure"));
}
events.assertContainsError(
"in deps attribute of my_rule rule //foo:analysis_failure: rule '//foo:missing' does not"
+ " exist");
// Null build
if (keepGoing) {
assertThrows(BuildFailedException.class, () -> buildTarget("//foo:analysis_failure"));
} else {
assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:analysis_failure"));
}
events.assertContainsError(
"in deps attribute of my_rule rule //foo:analysis_failure: rule '//foo:missing' does not"
+ " exist");
}
// Regression test for b/300391729.
@Test
public void executionFailure_keepGoing_doesNotSpamWarnings() throws Exception {
addOptions("--keep_going");
writeExecutionFailureAspectBzl();
write(
"foo/BUILD",
"""
cc_library(
name = "foo",
srcs = ["foo.cc"],
deps = [":bar"],
)
cc_library(
name = "bar",
srcs = ["bar.cc"],
)
""");
write("foo/foo.cc");
write("foo/bar.cc");
addOptions("--aspects=//foo:aspect.bzl%execution_err_aspect", "--output_groups=files");
assertThrows(BuildFailedException.class, () -> buildTarget("//foo/..."));
// No warnings.
events.assertNoWarnings();
}
// Regression test for b/301289073.
@Test
public void conflictCheck_doesNotTimeout() throws Exception {
addOptions("--keep_going");
write(
"foo/BUILD",
"""
BASE_SIZE = 500
TOP_SIZE = 100
genrule(
name = "base_0",
outs = ["base_0.txt"],
cmd = "touch $@",
)
[genrule(
name = "base_%s" % x,
srcs = ["base_%s.txt" % (x - 1)],
outs = ["base_%s.txt" % x],
cmd = "touch $@",
) for x in range(1, BASE_SIZE)]
[genrule(
name = "level_%s" % y,
srcs = ["base_%s.txt" % (
x,
) for x in range(0, BASE_SIZE)],
outs = ["level_%s.txt" % y],
cmd = "touch $@",
) for y in range(0, TOP_SIZE)]
genrule(
name = "conflict",
outs = ["conflict"],
cmd = "touch $@",
)
""");
write(
"foo/conflict/BUILD",
"""
genrule(
name = "conflict",
outs = ["conflict"],
cmd = "touch $@",
)
""");
// Building a set of targets with recursive dependencies that would trivially finish in time
// with memoization and time out without.
assertThrows(BuildFailedException.class, () -> buildTarget("//foo/..."));
events.assertContainsError("is a prefix of the other");
}
private void assertSingleAnalysisPhaseCompleteEventWithLabels(String... labels) {
assertThat(eventsSubscriber.getAnalysisPhaseCompleteEvents()).hasSize(1);
AnalysisPhaseCompleteEvent analysisPhaseCompleteEvent =
Iterables.getOnlyElement(eventsSubscriber.getAnalysisPhaseCompleteEvents());
assertThat(analysisPhaseCompleteEvent.getTimeInMs()).isGreaterThan(0);
assertThat(getLabelsOfAnalyzedTargets(analysisPhaseCompleteEvent))
.containsExactlyElementsIn(labels);
}
private static ImmutableSet<String> getLabelsOfAnalyzedTargets(AnalysisPhaseCompleteEvent event) {
return event.getTopLevelTargets().stream()
.map(x -> x.getOriginalLabel().getCanonicalForm())
.collect(toImmutableSet());
}
private RecordingOutErr divertInfoLogToOutErr() {
// Divert output into recorder:
RecordingOutErr recordedOutput = new RecordingOutErr();
this.outErr = recordedOutput;
divertLogging(
Level.INFO, outErr, ImmutableList.of(Logger.getLogger(SkymeldModule.class.getName())));
return recordedOutput;
}
private static final class EventsSubscriber {
private final List<TopLevelEntityAnalysisConcludedEvent> topLevelEntityAnalysisConcludedEvents =
Collections.synchronizedList(new ArrayList<>());
private final List<AnalysisPhaseCompleteEvent> analysisPhaseCompleteEvents =
Collections.synchronizedList(new ArrayList<>());
EventsSubscriber() {}
@Subscribe
void recordTopLevelEntityAnalysisConcludedEvent(TopLevelEntityAnalysisConcludedEvent event) {
topLevelEntityAnalysisConcludedEvents.add(event);
}
@Subscribe
void recordAnalysisPhaseCompleteEvent(AnalysisPhaseCompleteEvent event) {
analysisPhaseCompleteEvents.add(event);
}
public List<TopLevelEntityAnalysisConcludedEvent> getTopLevelEntityAnalysisConcludedEvents() {
return topLevelEntityAnalysisConcludedEvents;
}
public List<AnalysisPhaseCompleteEvent> getAnalysisPhaseCompleteEvents() {
return analysisPhaseCompleteEvents;
}
}
}