| // Copyright 2019 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.skyframe; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.eventbus.EventBus; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.analysis.BlazeDirectories; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.ServerDirectories; |
| import com.google.devtools.build.lib.clock.BlazeClock; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.events.EventCollector; |
| import com.google.devtools.build.lib.events.Reporter; |
| import com.google.devtools.build.lib.packages.BuildFileName; |
| import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions; |
| import com.google.devtools.build.lib.pkgcache.PackageCacheOptions; |
| import com.google.devtools.build.lib.pkgcache.PathPackageLocator; |
| import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction; |
| import com.google.devtools.build.lib.testutil.MoreAsserts; |
| import com.google.devtools.build.lib.testutil.Scratch; |
| import com.google.devtools.build.lib.testutil.TestPackageFactoryBuilderFactory; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; |
| import com.google.devtools.build.lib.vfs.FileSystem; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; |
| import com.google.devtools.build.skyframe.BuildDriver; |
| import com.google.devtools.build.skyframe.EvaluationContext; |
| import com.google.devtools.build.skyframe.EvaluationResult; |
| import com.google.devtools.build.skyframe.SkyFunction; |
| import com.google.devtools.build.skyframe.SkyFunctionName; |
| import com.google.devtools.build.skyframe.SkyKey; |
| import com.google.devtools.common.options.Options; |
| import com.google.devtools.common.options.OptionsProvider; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.UUID; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * Abstract base class for testing an implementation of a {@link SkyFunction} for {@link |
| * SkyFunctions#COLLECT_PACKAGES_UNDER_DIRECTORY}. |
| */ |
| public abstract class AbstractCollectPackagesUnderDirectoryTest { |
| protected FileSystem fileSystem; |
| protected Root root; |
| protected Path workingDir; |
| private Scratch scratch; |
| protected BlazeDirectories directories; |
| private EventCollector eventCollector; |
| private Reporter reporter; |
| protected ConfiguredRuleClassProvider ruleClassProvider; |
| private BuildDriver buildDriver; |
| |
| @Before |
| public void setUp() throws IOException { |
| fileSystem = new InMemoryFileSystem(); |
| workingDir = fileSystem.getPath(getWorkspacePathString()); |
| workingDir.createDirectoryAndParents(); |
| root = Root.fromPath(workingDir); |
| scratch = new Scratch(workingDir); |
| directories = |
| new BlazeDirectories( |
| new ServerDirectories( |
| fileSystem.getPath("/install"), |
| fileSystem.getPath("/output"), |
| fileSystem.getPath("/user_root")), |
| workingDir, |
| /*defaultSystemJavabase=*/ null, |
| /*productName=*/ "DummyProductNameForUnitTests"); |
| eventCollector = new EventCollector(); |
| reporter = new Reporter(new EventBus()); |
| reporter.addHandler(eventCollector); |
| } |
| |
| protected abstract String getWorkspacePathString(); |
| |
| protected abstract List<BuildFileName> getBuildFileNamesByPriority(); |
| |
| protected abstract ImmutableMap<SkyFunctionName, SkyFunction> getExtraSkyFunctions(); |
| |
| protected abstract SkyframeExecutorFactory makeSkyframeExecutorFactory(); |
| |
| @Test |
| public void noPackageErrors() throws Exception { |
| initBuildDriver(); |
| |
| scratch.file("BUILD"); |
| scratch.dir("a1/b1/c1"); |
| scratch.file("a1/b1/c1/BUILD"); |
| scratch.dir("a1/b1/c2"); |
| scratch.dir("a1/b2/c1"); |
| scratch.dir("a1/b2/c2"); |
| scratch.dir("a2/b1/c1"); |
| scratch.file("a2/b1/c1/BUILD"); |
| scratch.dir("a2/b1/c2"); |
| scratch.dir("a2/b2/c1"); |
| scratch.dir("a2/b2/c2"); |
| scratch.file("a2/b2/c2/BUILD"); |
| |
| { |
| CollectPackagesUnderDirectoryValue collectPackagesUnderDirectoryValue = |
| getCollectPackagesUnderDirectoryValue(""); |
| assertThat(collectPackagesUnderDirectoryValue.isDirectoryPackage()).isTrue(); |
| assertThat( |
| collectPackagesUnderDirectoryValue |
| .getSubdirectoryTransitivelyContainsPackagesOrErrors()) |
| .containsExactly( |
| rootedPath("tools"), Boolean.TRUE, |
| rootedPath("a1"), Boolean.TRUE, |
| rootedPath("a2"), Boolean.TRUE); |
| } |
| |
| { |
| CollectPackagesUnderDirectoryValue collectPackagesUnderDirectoryValue = |
| getCollectPackagesUnderDirectoryValue("a1"); |
| assertThat(collectPackagesUnderDirectoryValue.isDirectoryPackage()).isFalse(); |
| assertThat( |
| collectPackagesUnderDirectoryValue |
| .getSubdirectoryTransitivelyContainsPackagesOrErrors()) |
| .containsExactly(rootedPath("a1/b1"), Boolean.TRUE, rootedPath("a1/b2"), Boolean.FALSE); |
| } |
| |
| { |
| CollectPackagesUnderDirectoryValue collectPackagesUnderDirectoryValue = |
| getCollectPackagesUnderDirectoryValue("a2/b1"); |
| assertThat(collectPackagesUnderDirectoryValue.isDirectoryPackage()).isFalse(); |
| assertThat( |
| collectPackagesUnderDirectoryValue |
| .getSubdirectoryTransitivelyContainsPackagesOrErrors()) |
| .containsExactly( |
| rootedPath("a2/b1/c1"), Boolean.TRUE, rootedPath("a2/b1/c2"), Boolean.FALSE); |
| } |
| } |
| |
| @Test |
| public void packageErrors() throws Exception { |
| initBuildDriver(); |
| |
| scratch.dir("a1/b1"); |
| scratch.file("a1/b1/BUILD", "xxx"); |
| scratch.dir("a1/b2"); |
| scratch.dir("a2/b1"); |
| scratch.dir("a2/b2"); |
| scratch.file("a2/b2/BUILD", "yyy"); |
| |
| CollectPackagesUnderDirectoryValue collectPackagesUnderDirectoryValue = |
| getCollectPackagesUnderDirectoryValue(""); |
| assertThat(collectPackagesUnderDirectoryValue.isDirectoryPackage()).isFalse(); |
| assertThat( |
| collectPackagesUnderDirectoryValue |
| .getSubdirectoryTransitivelyContainsPackagesOrErrors()) |
| .containsExactly( |
| rootedPath("tools"), Boolean.TRUE, |
| rootedPath("a1"), Boolean.TRUE, |
| rootedPath("a2"), Boolean.TRUE); |
| MoreAsserts.assertContainsEvent(eventCollector, "Loading package: a1/b1"); |
| MoreAsserts.assertContainsEvent(eventCollector, "a1/b1/BUILD:1:1: name 'xxx' is not defined"); |
| MoreAsserts.assertContainsEvent(eventCollector, "Loading package: a2/b2"); |
| MoreAsserts.assertContainsEvent(eventCollector, "a2/b2/BUILD:1:1: name 'yyy' is not defined"); |
| } |
| |
| @Test |
| public void symlinks() throws Exception { |
| initBuildDriver(); |
| |
| Path a1DirPath = scratch.dir("a1"); |
| scratch.dir("a1/b1/c1"); |
| Path a1CircularPath = scratch.resolve("a1/circular"); |
| FileSystemUtils.ensureSymbolicLink(a1CircularPath, a1CircularPath); |
| scratch.file("a1/b1/c1/BUILD"); |
| FileSystemUtils.ensureSymbolicLink(scratch.resolve("a2"), a1DirPath); |
| scratch.dir("a3"); |
| scratch.file( |
| "a3/DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN"); |
| FileSystemUtils.ensureSymbolicLink(scratch.resolve("a3/dirlink"), a1DirPath); |
| |
| CollectPackagesUnderDirectoryValue collectPackagesUnderDirectoryValue = |
| getCollectPackagesUnderDirectoryValue(""); |
| assertThat(collectPackagesUnderDirectoryValue.isDirectoryPackage()).isFalse(); |
| assertThat( |
| collectPackagesUnderDirectoryValue |
| .getSubdirectoryTransitivelyContainsPackagesOrErrors()) |
| .containsExactly( |
| rootedPath("tools"), Boolean.TRUE, |
| rootedPath("a1"), Boolean.TRUE, |
| rootedPath("a2"), Boolean.TRUE, |
| rootedPath("a3"), Boolean.FALSE); |
| MoreAsserts.assertContainsEvent(eventCollector, "Loading package: a1/b1/c1"); |
| MoreAsserts.assertContainsEvent(eventCollector, "Loading package: a2/b1/c1"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a3/b1/c1"); |
| MoreAsserts.assertContainsEvent( |
| eventCollector, |
| "Failed to get information about path, for a1/circular, skipping: Symlink cycle"); |
| MoreAsserts.assertContainsEvent( |
| eventCollector, |
| "Failed to get information about path, for a2/circular, skipping: Symlink cycle"); |
| } |
| |
| @Test |
| public void excludedPaths() throws Exception { |
| initBuildDriver(); |
| |
| scratch.dir("a1/b1/c1"); |
| scratch.file("a1/b1/c1/BUILD"); |
| scratch.dir("a1/b1/c2"); |
| scratch.file("a1/b1/c2/BUILD"); |
| scratch.dir("a1/b2/c1"); |
| scratch.file("a1/b2/c1/BUILD"); |
| scratch.dir("a1/b2/c2"); |
| scratch.file("a1/b2/c2/BUILD"); |
| scratch.dir("a2/b1/c1"); |
| scratch.file("a2/b1/c1/BUILD"); |
| scratch.dir("a2/b1/c2"); |
| scratch.file("a2/b1/c2/BUILD"); |
| scratch.dir("a2/b2/c1"); |
| scratch.file("a2/b2/c1/BUILD"); |
| scratch.dir("a2/b2/c2"); |
| scratch.file("a2/b2/c2/BUILD"); |
| |
| CollectPackagesUnderDirectoryValue collectPackagesUnderDirectoryValue = |
| getCollectPackagesUnderDirectoryValue( |
| "", |
| /*excludedPaths=*/ ImmutableSet.of( |
| PathFragment.create("a1"), |
| PathFragment.create("a2/b1"), |
| PathFragment.create("a2/b2/c2"))); |
| assertThat(collectPackagesUnderDirectoryValue.isDirectoryPackage()).isFalse(); |
| // There is not supposed to be a map entry for excluded subdirectories. |
| assertThat( |
| collectPackagesUnderDirectoryValue |
| .getSubdirectoryTransitivelyContainsPackagesOrErrors()) |
| .containsExactly( |
| rootedPath("tools"), Boolean.TRUE, |
| rootedPath("a2"), Boolean.TRUE); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a1/b1/c1"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a1/b1/c2"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a1/b2/c1"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a1/b1/c2"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a2/b1/c1"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a2/b1/c2"); |
| MoreAsserts.assertContainsEvent(eventCollector, "Loading package: a2/b2/c1"); |
| MoreAsserts.assertDoesNotContainEvent(eventCollector, "Loading package: a2/b2/c2"); |
| } |
| |
| private void initBuildDriver() throws AbruptExitException, InterruptedException, IOException { |
| PathPackageLocator pathPackageLocator = |
| PathPackageLocator.createWithoutExistenceCheck( |
| directories.getOutputBase(), ImmutableList.of(root), getBuildFileNamesByPriority()); |
| PackageCacheOptions packageCacheOptions = Options.getDefaults(PackageCacheOptions.class); |
| packageCacheOptions.packagePath = ImmutableList.of(getWorkspacePathString()); |
| scratch.file("tools/BUILD"); |
| scratch.file("tools/empty_prelude.bzl"); |
| ruleClassProvider = |
| new ConfiguredRuleClassProvider.Builder() |
| .setRunfilesPrefix("workspace") |
| .setPrelude("//tools:empty_prelude.bzl") |
| .build(); |
| SkyframeExecutor skyframeExecutor = |
| makeSkyframeExecutorFactory() |
| .create( |
| TestPackageFactoryBuilderFactory.getInstance() |
| .builder(directories) |
| .build(ruleClassProvider, fileSystem), |
| fileSystem, |
| directories, |
| new ActionKeyContext(), |
| /*workspaceStatusActionFactory=*/ null, |
| /*diffAwarenessFactories=*/ ImmutableList.of(), |
| getExtraSkyFunctions(), |
| /*customDirtinessCheckers=*/ ImmutableList.of(), |
| /*managedDirectoriesKnowledge=*/ null); |
| skyframeExecutor.injectExtraPrecomputedValues( |
| ImmutableList.of( |
| PrecomputedValue.injected( |
| RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE, Optional.absent()), |
| PrecomputedValue.injected( |
| RepositoryDelegatorFunction.REPOSITORY_OVERRIDES, ImmutableMap.of()), |
| PrecomputedValue.injected( |
| RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING, |
| RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY))); |
| skyframeExecutor.sync( |
| reporter, |
| packageCacheOptions, |
| pathPackageLocator, |
| Options.getDefaults(StarlarkSemanticsOptions.class), |
| UUID.randomUUID(), |
| /*clientEnv=*/ ImmutableMap.of(), |
| new TimestampGranularityMonitor(BlazeClock.instance()), |
| OptionsProvider.EMPTY); |
| buildDriver = skyframeExecutor.getDriver(); |
| } |
| |
| private CollectPackagesUnderDirectoryValue getCollectPackagesUnderDirectoryValue(String directory) |
| throws InterruptedException { |
| return getCollectPackagesUnderDirectoryValue(directory, /*excludedPaths=*/ ImmutableSet.of()); |
| } |
| |
| private CollectPackagesUnderDirectoryValue getCollectPackagesUnderDirectoryValue( |
| String directory, ImmutableSet<PathFragment> excludedPaths) throws InterruptedException { |
| SkyKey key = |
| CollectPackagesUnderDirectoryValue.key( |
| RepositoryName.MAIN, rootedPath(directory), excludedPaths); |
| return evaluate(key).get(key); |
| } |
| |
| private RootedPath rootedPath(String relativePath) { |
| return RootedPath.toRootedPath(root, PathFragment.create(relativePath)); |
| } |
| |
| private EvaluationResult<CollectPackagesUnderDirectoryValue> evaluate(SkyKey key) |
| throws InterruptedException { |
| EvaluationContext evaluationContext = |
| EvaluationContext.newBuilder() |
| .setKeepGoing(true) |
| .setNumThreads(1) |
| .setEventHander(new Reporter(new EventBus(), reporter)) |
| .build(); |
| return buildDriver.evaluate(ImmutableList.of(key), evaluationContext); |
| } |
| } |