| // Copyright 2024 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.truth.Truth.assertThat; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; |
| import com.google.devtools.build.lib.runtime.commands.CqueryCommand; |
| import com.google.devtools.build.lib.skyframe.SkyfocusState.WorkingSetType; |
| import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Skyfocus integration tests */ |
| @RunWith(JUnit4.class) |
| public final class SkyfocusIntegrationTest extends BuildIntegrationTestCase { |
| |
| @Override |
| protected void setupOptions() throws Exception { |
| super.setupOptions(); |
| addOptions("--experimental_enable_skyfocus"); |
| } |
| |
| @Test |
| public void cquery_doesNotTriggerSkyfocus() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| runtimeWrapper.newCommand(CqueryCommand.class); |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()).isEmpty(); |
| } |
| |
| @Test |
| public void workingSet_canBeUsedWithBuildCommandAndNoTargets() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget(); |
| } |
| |
| @Test |
| public void workingSet_canBeUsedWithBuildCommandWithTargets() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/x.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetType()) |
| .isEqualTo(WorkingSetType.USER_DEFINED); |
| } |
| |
| @Test |
| public void workingSet_canBeAutomaticallyDerivedUsingTopLevelTargetPackage() throws Exception { |
| write("hello/x.txt", "x"); |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "world/y.txt"], |
| outs = ["out"], |
| cmd = "cat $(location x.txt) $(location world/y.txt) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt", "hello/world", "hello/world/y.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().verificationSet()).isNotEmpty(); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetType()) |
| .isEqualTo(WorkingSetType.DERIVED); |
| } |
| |
| @Test |
| public void workingSet_canBeAutomaticallyDerivedUsingProjectFile() throws Exception { |
| addOptions("--experimental_enable_scl_dialect"); |
| |
| write("hello/x.txt", "x"); |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "world/y.txt", "//somewhere/else:files"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| // Files under //somewhere/else will be included because of this PROJECT.scl file. |
| write( |
| "hello/PROJECT.scl", |
| """ |
| active_directories = { "default": [ "hello", "somewhere/else", "not/used" ] } |
| """); |
| |
| write("somewhere/else/file.txt", "some content"); |
| write( |
| "somewhere/else/BUILD", |
| """ |
| filegroup(name = "files", srcs = ["file.txt"]) |
| """); |
| |
| // Even though the PROJECT.scl file specified //not/used, this is not a dependency of |
| // the focused target, hence it's not part of the working set. |
| write("not/used/BUILD"); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly( |
| "hello", |
| "hello/PROJECT.scl", |
| "hello/BUILD", |
| "hello/x.txt", |
| "hello/world", |
| "hello/world/y.txt", |
| "somewhere/else", |
| "somewhere/else/BUILD", |
| "somewhere/else/file.txt"); |
| } |
| |
| @Test |
| public void workingSet_ignoresTopLevelPackageDirectoriesWhenUsingProjectFile() throws Exception { |
| addOptions("--experimental_enable_scl_dialect"); |
| |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "//somewhere/else:files"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| write( |
| "hello/PROJECT.scl", |
| """ |
| active_directories = {"default": ["somewhere/else"] } |
| """); |
| |
| write("somewhere/else/file.txt", "some content"); |
| write( |
| "somewhere/else/BUILD", |
| """ |
| filegroup(name = "files", srcs = ["file.txt"]) |
| """); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("somewhere/else", "somewhere/else/BUILD", "somewhere/else/file.txt"); |
| } |
| |
| @Test |
| public void workingSet_projectFileCanHandleExcludedDirectories() throws Exception { |
| addOptions("--experimental_enable_scl_dialect"); |
| |
| write("hello/x.txt", "x"); |
| write("hello/world/y.txt", "y"); |
| write("hello/world/again/z.txt", "z"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "world/y.txt", "world/again/z.txt", "//somewhere/else:files"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| write( |
| "hello/PROJECT.scl", |
| """ |
| active_directories = { |
| "default": [ |
| "hello", # included |
| "-hello/world", # excluded |
| "hello/world/again", # included |
| "-somewhere/else", # excluded |
| ], |
| } |
| """); |
| |
| write("somewhere/else/file.txt", "some content"); |
| write( |
| "somewhere/else/BUILD", |
| """ |
| filegroup(name = "files", srcs = ["file.txt"]) |
| """); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly( |
| "hello", |
| "hello/PROJECT.scl", |
| "hello/BUILD", |
| "hello/x.txt", |
| "hello/world/again", |
| "hello/world/again/z.txt"); |
| } |
| |
| @Test |
| public void workingSet_skyfocusDoesNotRunIfDerivedWorkingSetIsUnchanged() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| assertContainsEvent("Focusing on"); |
| |
| events.clear(); |
| |
| buildTarget("//hello:target"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| assertDoesNotContainEvent("Focusing on"); |
| } |
| |
| @Test |
| public void workingSet_derivedWorkingSetCanBeOverwrittenByUserDefinedWorkingSet() |
| throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetType()) |
| .isEqualTo(WorkingSetType.DERIVED); |
| |
| addOptions("--experimental_working_set=hello/x.txt"); |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/x.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetType()) |
| .isEqualTo(WorkingSetType.USER_DEFINED); |
| |
| resetOptions(); |
| setupOptions(); |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/x.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetType()) |
| .isEqualTo(WorkingSetType.USER_DEFINED); |
| } |
| |
| @Test |
| public void workingSet_isRetainedAcrossInvocations() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/x.txt"); |
| |
| resetOptions(); |
| setupOptions(); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/x.txt"); |
| } |
| |
| @Test |
| public void workingSet_derivedWorkingSetChangesWhenTargetHasNewDependency() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("automatically deriving working set"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| |
| assertContents("x", "//hello:target"); |
| |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| write("hello/y.txt", "y"); |
| buildTarget("//hello:target"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt", "hello/y.txt"); |
| |
| assertContents("x\ny", "//hello:target"); |
| } |
| |
| @Test |
| public void workingSet_derivedWorkingSetChangesWhenTargetHasNewGlobDependency() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = glob(["*.txt"]), |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello:target"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| assertContents("x", "//hello:target"); |
| |
| write("hello/y.txt", "y"); |
| buildTarget("//hello:target"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt", "hello/y.txt"); |
| assertContents("x\ny", "//hello:target"); |
| } |
| |
| @Test |
| public void workingSet_derivedWorkingSetChangesWhenPackageHasANewTarget() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello:all"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| |
| genrule( |
| name = "target2", |
| srcs = ["y.txt"], |
| outs = ["out2"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| write("hello/y.txt", "y"); |
| |
| buildTarget("//hello:all"); |
| |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt", "hello/y.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly( |
| Label.parseCanonicalUnchecked("//hello:target"), |
| Label.parseCanonicalUnchecked("//hello:target2")); |
| } |
| |
| @Test |
| public void workingSet_canBeAutomaticallyDerivedWithoutSkymeld() throws Exception { |
| addOptions("--noexperimental_merged_skyframe_analysis_execution"); |
| write("hello/x.txt", "x"); |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "world/y.txt"], |
| outs = ["out"], |
| cmd = "cat $(location x.txt) $(location world/y.txt) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContainsEvent("automatically deriving working set"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt", "hello/world", "hello/world/y.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().verificationSet()).isNotEmpty(); |
| } |
| |
| @Test |
| public void workingSet_derivationDoesNotIncludeFilesInSubpackage() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| write("hello/world/y.txt", "y"); |
| write("hello/world/BUILD", ""); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("automatically deriving working set"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| } |
| |
| @Test |
| public void workingSet_canBeAutomaticallyDerivedUsingMultipleTopLevelTargetPackages() |
| throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/world/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["y.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContainsEvent("automatically deriving working set"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly( |
| Label.parseCanonicalUnchecked("//hello:target"), |
| Label.parseCanonicalUnchecked("//hello/world:target")); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly( |
| "hello", |
| "hello/BUILD", |
| "hello/x.txt", |
| "hello/world", |
| "hello/world/BUILD", |
| "hello/world/y.txt"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().verificationSet()).isNotEmpty(); |
| } |
| |
| @Test |
| public void workingSet_shouldBeDerivedAndRetainedByTopLevelTarget() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| write("world/y.txt", "y"); |
| write( |
| "world/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["y.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly(Label.parseCanonicalUnchecked("//hello:target")); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| |
| assertContainsEvent("automatically deriving working set"); |
| assertContainsEvent("Focusing on"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().verificationSet()).isNotEmpty(); |
| |
| events.collector().clear(); |
| |
| buildTarget("//world/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().focusedTargetLabels()) |
| .containsExactly( |
| Label.parseCanonicalUnchecked("//hello:target"), |
| Label.parseCanonicalUnchecked("//world:target")); |
| assertContainsEvent("automatically deriving working set"); |
| assertContainsEvent("Focusing on"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly( |
| "hello", "hello/BUILD", "hello/x.txt", "world", "world/BUILD", "world/y.txt"); |
| } |
| |
| @Test |
| public void workingSet_derivedWorkingSetBuildsForTargetThenRdep() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "//hello/world:dep"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/world/BUILD", |
| """ |
| genrule( |
| name = "dep", |
| srcs = ["y.txt"], |
| outs = ["dep.txt"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/world:dep"); |
| assertContainsEvent("Focusing on"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/world", "hello/world/BUILD", "hello/world/y.txt"); |
| |
| events.collector().clear(); |
| |
| buildTarget("//hello:target"); |
| assertContainsEvent("Focusing on"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly( |
| "hello/world", |
| "hello/world/BUILD", |
| "hello/world/y.txt", |
| "hello", |
| "hello/BUILD", |
| "hello/x.txt"); |
| } |
| |
| @Test |
| public void workingSet_sharedDepBetweenTwoTopLevelTargetsIsKept() throws Exception { |
| // A -> C |
| // B -> C |
| // After building A, CT(C) will be dropped, but not CT(C/in.txt). |
| // After building B, A's nodes should not be affected. |
| write("A/in.txt", "A"); |
| write( |
| "A/BUILD", |
| """ |
| genrule( |
| name = "A", |
| srcs = ["in.txt", "//C:C.txt"], |
| outs = ["A"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| write("B/in.txt", "B"); |
| write( |
| "B/BUILD", |
| """ |
| genrule( |
| name = "B", |
| srcs = ["in.txt", "//C:C.txt"], |
| outs = ["B"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| write("C/in.txt", "C"); |
| write( |
| "C/BUILD", |
| """ |
| genrule( |
| name = "C", |
| srcs = ["in.txt"], |
| outs = ["C.txt"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//A"); |
| |
| assertThat(getAllConfiguredTargets()) |
| .containsAtLeast( |
| getConfiguredTarget("//A:in.txt"), |
| getConfiguredTarget("//A:A"), |
| getConfiguredTarget("//C:C.txt")); |
| assertThat( |
| SkyframeExecutorTestUtils.getExistingConfiguredTarget( |
| getSkyframeExecutor(), label("//C"), getTargetConfiguration())) |
| .isNull(); |
| |
| buildTarget("//B"); |
| |
| assertThat(getAllConfiguredTargets()) |
| .containsAtLeast( |
| getConfiguredTarget("//A:in.txt"), // nodes from the previous build should still be kept |
| getConfiguredTarget("//A:A"), |
| getConfiguredTarget("//B:in.txt"), |
| getConfiguredTarget("//B:B"), |
| getConfiguredTarget("//C:C.txt")); |
| |
| assertThat( |
| SkyframeExecutorTestUtils.getExistingConfiguredTarget( |
| getSkyframeExecutor(), label("//C"), getTargetConfiguration())) |
| .isNull(); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("A", "A/BUILD", "A/in.txt", "B", "B/BUILD", "B/in.txt"); |
| } |
| |
| @Test |
| public void workingSet_configChangesAreHandledWithDerivedWorkingSet() throws Exception { |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContents("x\ny", "//hello:target"); |
| |
| addOptions("--compilation_mode=opt", "--experimental_skyfocus_handling_strategy=warn"); |
| buildTarget("//hello/..."); |
| assertContainsEvent("detected changes to the build configuration"); |
| assertContainsEvent("will be discarding the analysis cache"); |
| assertContainsEvent("Focusing on"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt", "hello/y.txt"); |
| } |
| |
| @Test |
| public void workingSet_configChangesAreHandledWithExplicitWorkingSet() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContents("x\ny", "//hello:target"); |
| |
| addOptions("--compilation_mode=opt", "--experimental_skyfocus_handling_strategy=warn"); |
| buildTarget("//hello/..."); |
| assertContainsEvent("detected changes to the build configuration"); |
| assertContainsEvent("will be discarding the analysis cache"); |
| assertContainsEvent("Focusing on"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello/x.txt"); |
| } |
| |
| @Test |
| public void workingSet_configChangesAreHandledStrictly() throws Exception { |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| |
| addOptions("--compilation_mode=opt"); |
| AbruptExitException e = |
| assertThrows(AbruptExitException.class, () -> buildTarget("//hello/...")); |
| assertThat(e).hasMessageThat().contains("detected changes to the build configuration"); |
| } |
| |
| @Test |
| public void workingSet_withFiles_correctlyRebuilds() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContents("x\ny", "//hello:target"); |
| |
| write("hello/x.txt", "x2"); |
| buildTarget("//hello/..."); |
| assertContents("x2\ny", "//hello:target"); |
| } |
| |
| @Test |
| public void workingSet_withDirs_correctlyRebuilds() throws Exception { |
| /* |
| * Setting directories in the working set works, because the rdep edges look like: |
| * |
| * FILE_STATE:[dir] -> FILE:[dir] -> FILE:[dir/BUILD], FILE:[dir/file.txt] |
| * |
| * ...and the FILE SkyKeys directly depend on their respective FILE_STATE SkyKeys, |
| * which are the nodes that are invalidated by SkyframeExecutor#handleDiffs |
| * at the start of every build, and are also kept by Skyfocus. |
| * |
| * In other words, defining a working set of directories will automatically |
| * include all the files under those directories for focusing. |
| */ |
| |
| // Define working set to be a directory, not file |
| addOptions("--experimental_working_set=hello"); |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContents("x\ny", "//hello:target"); |
| |
| write("hello/x.txt", "x2"); |
| buildTarget("//hello/..."); |
| // Correctly rebuilds referenced source file |
| assertContents("x2\ny", "//hello:target"); |
| |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "y_only", |
| srcs = ["y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| buildTarget("//hello/..."); |
| // Correctly reanalyzes BUILD file |
| assertContents("y", "//hello:y_only"); |
| } |
| |
| @Test |
| public void workingSet_nestedDirs_correctlyRebuilds() throws Exception { |
| // Define working set to be the parent package |
| addOptions("--experimental_working_set=hello"); |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "//hello/world:target"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/world/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertContents("x\ny", "//hello:target"); |
| |
| write("hello/x.txt", "x2"); |
| buildTarget("//hello/..."); |
| // Rebuilds when parent package's source file changes |
| assertContents("x2\ny", "//hello:target"); |
| |
| write("hello/world/y.txt", "y2"); |
| buildTarget("//hello/..."); |
| // Rebuilds when child package's source file changes |
| assertContents("x2\ny2", "//hello:target"); |
| } |
| |
| @Test |
| public void newNonFocusedTargets_canBeBuilt() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "g", |
| srcs = ["x.txt"], |
| outs = ["g.txt"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| genrule( |
| name = "g2", |
| srcs = ["x.txt"], |
| outs = ["g2.txt"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| genrule( |
| name = "g3", |
| outs = ["g3.txt"], |
| cmd = "touch $@", |
| ) |
| """); |
| buildTarget("//hello:g"); |
| write("hello/x.txt", "x2"); |
| buildTarget("//hello:g"); |
| buildTarget("//hello:g2"); |
| buildTarget("//hello:g3"); |
| } |
| |
| @Test |
| public void skyfocus_doesNotRun_forUnsuccessfulBuilds() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| # error |
| ) |
| """); |
| TargetParsingException e = |
| assertThrows(TargetParsingException.class, () -> buildTarget("//hello/...")); |
| |
| assertThat(e).hasMessageThat().contains("Package 'hello' contains errors"); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().enabled()).isTrue(); |
| assertThat(getSkyframeExecutor().getSkyfocusState().verificationSet()).isEmpty(); |
| } |
| |
| @Test |
| public void editingNonWorkingSet_inSameDir_failsTheBuild() throws Exception { |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| addOptions("--experimental_working_set=hello/x.txt"); |
| buildTarget("//hello/..."); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(1); |
| |
| write("hello/y.txt", "y2"); |
| AbruptExitException e = |
| assertThrows(AbruptExitException.class, () -> buildTarget("//hello/...")); |
| assertThat(e).hasMessageThat().contains("detected changes outside of the working set"); |
| assertThat(e).hasMessageThat().contains("hello/y.txt"); |
| |
| addOptions("--experimental_working_set=hello/x.txt,hello/y.txt"); |
| buildTarget("//hello/..."); |
| assertContents("x\ny2", "//hello:target"); |
| } |
| |
| @Test |
| public void editingNonWorkingSet_throughDep_failsTheBuild() throws Exception { |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "dep", |
| srcs = ["x.txt"], |
| outs = ["dep.txt"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| genrule( |
| name = "target", |
| srcs = ["dep.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| addOptions("--experimental_working_set=hello/y.txt"); |
| buildTarget("//hello/..."); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(1); |
| |
| write("hello/x.txt", "x2"); |
| AbruptExitException e = |
| assertThrows(AbruptExitException.class, () -> buildTarget("//hello/...")); |
| assertThat(e).hasMessageThat().contains("detected changes outside of the working set"); |
| assertThat(e).hasMessageThat().contains("hello/x.txt"); |
| |
| addOptions("--experimental_working_set=hello/x.txt,hello/y.txt"); |
| buildTarget("//hello/..."); |
| assertContents("x2\ny", "//hello:target"); |
| } |
| |
| @Test |
| public void editingNonWorkingSet_inSiblingDir_failsTheBuild() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "//world:target"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| write("world/y.txt", "y"); |
| write( |
| "world/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| addOptions("--experimental_working_set=hello/x.txt"); |
| buildTarget("//hello/..."); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(1); |
| |
| write("world/y.txt", "y2"); |
| AbruptExitException e = |
| assertThrows(AbruptExitException.class, () -> buildTarget("//hello/...")); |
| assertThat(e).hasMessageThat().contains("detected changes outside of the working set"); |
| assertThat(e).hasMessageThat().contains("world/y.txt"); |
| |
| addOptions("--experimental_working_set=hello/x.txt,world/y.txt"); |
| buildTarget("//hello/..."); |
| assertContents("x\ny2", "//hello:target"); |
| } |
| |
| @Test |
| public void editingNonWorkingSet_inParentDir_failsTheBuild() throws Exception { |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "//hello/world:target"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| write("hello/world/y.txt", "y"); |
| write( |
| "hello/world/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| addOptions("--experimental_working_set=hello/world"); |
| buildTarget("//hello/..."); |
| |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(1); |
| |
| write("hello/x.txt", "x2"); |
| AbruptExitException e = |
| assertThrows(AbruptExitException.class, () -> buildTarget("//hello/...")); |
| assertThat(e).hasMessageThat().contains("detected changes outside of the working set"); |
| assertThat(e).hasMessageThat().contains("hello/x.txt"); |
| |
| addOptions("--experimental_working_set=hello"); |
| buildTarget("//hello/..."); |
| assertContents("x2\ny", "//hello:target"); |
| } |
| |
| @Test |
| public void workingSet_reduced_withoutReanalysis() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt,hello/y.txt"); |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(2); |
| |
| resetOptions(); |
| setupOptions(); |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x2"); |
| |
| buildTarget("//hello/..."); |
| assertDoesNotContainEvent("discarding analysis cache"); |
| assertContents("x2\ny", "//hello:target"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(1); |
| } |
| |
| @Test |
| public void workingSet_expanded_withReanalysis() throws Exception { |
| addOptions("--experimental_working_set=hello/x.txt"); |
| write("hello/x.txt", "x"); |
| write("hello/y.txt", "y"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt", "y.txt"], |
| outs = ["out"], |
| cmd = "cat $(SRCS) > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(1); |
| |
| resetOptions(); |
| setupOptions(); |
| addOptions("--experimental_working_set=hello/x.txt,hello/y.txt"); |
| write("hello/x.txt", "x2"); |
| |
| buildTarget("//hello/..."); |
| assertContainsEvent("discarding analysis cache"); |
| assertContents("x2\ny", "//hello:target"); |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSet()).hasSize(2); |
| } |
| |
| @Test |
| public void actionCache_canBeNull() throws Exception { |
| addOptions("--nouse_action_cache"); |
| write("hello/x.txt", "x"); |
| write( |
| "hello/BUILD", |
| """ |
| genrule( |
| name = "target", |
| srcs = ["x.txt"], |
| outs = ["out"], |
| cmd = "cat $< > $@", |
| ) |
| """); |
| |
| buildTarget("//hello/..."); // does not crash. |
| assertThat(getSkyframeExecutor().getSkyfocusState().workingSetStrings()) |
| .containsExactly("hello", "hello/BUILD", "hello/x.txt"); |
| } |
| } |