aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Kristina Chodorow <kchodorow@google.com>2016-07-22 15:18:07 +0000
committerGravatar John Cater <jcater@google.com>2016-07-22 20:10:29 +0000
commit36a6c17629e76872a876cd313623246d4d0aa082 (patch)
tree0178e6b8487cfd00f7c7864f2c20620da3dbcb9d
parent4545ef538664563d284ceb7d19d6dc357497b2d0 (diff)
Create a symlink with the right workspace name under the execroot
The execution root currently uses the basename of the workspace directory for the workspace name, not the name in the WORKSPACE file. (For example, if our sources were in /path/to/foo and our WORKSPACE file had workspace(name = "bar"), our execution root would look like execroot/foo.) This creates a symlink bar -> foo, so that accessing ../repo_name actually works for the main repository. RELNOTES[INC]: The main repository's execution root is under the main repository's workspace name, not the source directory's basename. This shouldn't have any effect on most builds, but it's possible it could break someone doing weird things with paths in actions. -- MOS_MIGRATED_REVID=128175455
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/BuildView.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java22
-rw-r--r--src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java20
-rw-r--r--src/main/java/com/google/devtools/build/lib/pkgcache/LoadingResult.java9
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java23
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java11
-rw-r--r--src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java31
-rw-r--r--src/test/shell/bazel/BUILD7
-rwxr-xr-xsrc/test/shell/bazel/execroot_test.sh43
10 files changed, 166 insertions, 31 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index 2f5f70f01d..e7c4ac468c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -283,7 +283,8 @@ public class BuildView {
ImmutableList.<ConfiguredTarget>of(),
ImmutableList.<ConfiguredTarget>of(),
null,
- ImmutableMap.<PackageIdentifier, Path>of());
+ ImmutableMap.<PackageIdentifier, Path>of(),
+ "");
private final ImmutableList<ConfiguredTarget> targetsToBuild;
@Nullable private final ImmutableList<ConfiguredTarget> targetsToTest;
@@ -295,6 +296,7 @@ public class BuildView {
@Nullable private final TopLevelArtifactContext topLevelContext;
private final ImmutableList<AspectValue> aspects;
private final ImmutableMap<PackageIdentifier, Path> packageRoots;
+ private final String workspaceName;
private AnalysisResult(
Collection<ConfiguredTarget> targetsToBuild,
@@ -306,7 +308,8 @@ public class BuildView {
Collection<ConfiguredTarget> parallelTests,
Collection<ConfiguredTarget> exclusiveTests,
TopLevelArtifactContext topLevelContext,
- ImmutableMap<PackageIdentifier, Path> packageRoots) {
+ ImmutableMap<PackageIdentifier, Path> packageRoots,
+ String workspaceName) {
this.targetsToBuild = ImmutableList.copyOf(targetsToBuild);
this.aspects = ImmutableList.copyOf(aspects);
this.targetsToTest = targetsToTest == null ? null : ImmutableList.copyOf(targetsToTest);
@@ -317,6 +320,7 @@ public class BuildView {
this.exclusiveTests = ImmutableSet.copyOf(exclusiveTests);
this.topLevelContext = topLevelContext;
this.packageRoots = packageRoots;
+ this.workspaceName = workspaceName;
}
/**
@@ -386,6 +390,10 @@ public class BuildView {
public TopLevelArtifactContext getTopLevelContext() {
return topLevelContext;
}
+
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
}
@@ -602,7 +610,8 @@ public class BuildView {
parallelTests,
exclusiveTests,
topLevelOptions,
- skyframeAnalysisResult.getPackageRoots());
+ skyframeAnalysisResult.getPackageRoots(),
+ loadingResult.getWorkspaceName());
}
private static NestedSet<Artifact> getBaselineCoverageArtifacts(
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index 1f54bcc0b5..cf838bce49 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -51,7 +51,6 @@ import com.google.devtools.build.lib.actions.SimpleActionContextProvider;
import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.actions.TestExecException;
import com.google.devtools.build.lib.actions.cache.ActionCache;
-import com.google.devtools.build.lib.analysis.BuildView;
import com.google.devtools.build.lib.analysis.BuildView.AnalysisResult;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.SymlinkTreeActionContext;
@@ -94,7 +93,6 @@ import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
-
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -341,7 +339,7 @@ public class ExecutionTool {
TopLevelArtifactContext topLevelArtifactContext)
throws BuildFailedException, InterruptedException, TestExecException, AbruptExitException {
Stopwatch timer = Stopwatch.createStarted();
- prepare(packageRoots);
+ prepare(packageRoots, analysisResult.getWorkspaceName());
ActionGraph actionGraph = analysisResult.getActionGraph();
@@ -363,9 +361,10 @@ public class ExecutionTool {
if (targetConfigurations.size() == 1) {
String productName = runtime.getProductName();
OutputDirectoryLinksUtils.createOutputDirectoryLinks(
- env.getWorkspaceName(), env.getWorkspace(), getExecRoot(),
- env.getOutputPath(), getReporter(), targetConfiguration,
- request.getBuildOptions().getSymlinkPrefix(productName), productName);
+ env.getWorkspaceName(), env.getWorkspace(),
+ getExecRoot(), env.getOutputPath(), getReporter(),
+ targetConfiguration, request.getBuildOptions().getSymlinkPrefix(productName),
+ productName);
}
ActionCache actionCache = getActionCache();
@@ -499,7 +498,7 @@ public class ExecutionTool {
}
}
- private void prepare(ImmutableMap<PackageIdentifier, Path> packageRoots)
+ private void prepare(ImmutableMap<PackageIdentifier, Path> packageRoots, String workspaceName)
throws ExecutorInitException {
// Prepare for build.
Profiler.instance().markPhase(ProfilePhase.PREPARE);
@@ -510,7 +509,8 @@ public class ExecutionTool {
// Plant the symlink forest.
try {
new SymlinkForest(
- packageRoots, getExecRoot(), runtime.getProductName()).plantSymlinkForest();
+ packageRoots, getExecRoot(), runtime.getProductName(), workspaceName)
+ .plantSymlinkForest();
} catch (IOException e) {
throw new ExecutorInitException("Source forest creation failed", e);
}
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java b/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java
index 5c8ff90565..9d57a44466 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/SymlinkForest.java
@@ -43,13 +43,16 @@ class SymlinkForest {
private final ImmutableMap<PackageIdentifier, Path> packageRoots;
private final Path workspace;
+ private final String workspaceName;
private final String productName;
private final String[] prefixes;
SymlinkForest(
- ImmutableMap<PackageIdentifier, Path> packageRoots, Path workspace, String productName) {
+ ImmutableMap<PackageIdentifier, Path> packageRoots, Path workspace, String productName,
+ String workspaceName) {
this.packageRoots = packageRoots;
this.workspace = workspace;
+ this.workspaceName = workspaceName;
this.productName = productName;
this.prefixes = new String[] { ".", "_", productName + "-"};
}
@@ -216,6 +219,23 @@ class SymlinkForest {
}
}
}
+
+ symlinkCorrectWorkspaceName();
+ }
+
+ /**
+ * Right now, the execution root is under the basename of the source directory, not the name
+ * defined in the WORKSPACE file. Thus, this adds a symlink with the WORKSPACE's workspace name
+ * to the old-style execution root.
+ * TODO(kchodorow): get rid of this once exec root is always under the WORKSPACE's workspace
+ * name.
+ * @throws IOException
+ */
+ private void symlinkCorrectWorkspaceName() throws IOException {
+ Path correctDirectory = workspace.getParentDirectory().getRelative(workspaceName);
+ if (!correctDirectory.exists()) {
+ correctDirectory.createSymbolicLink(workspace);
+ }
}
private static PackageIdentifier getParent(PackageIdentifier packageIdentifier) {
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java b/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java
index 09698e6176..741e1be26a 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/LegacyLoadingPhaseRunner.java
@@ -27,6 +27,7 @@ import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.DelegatingEventHandler;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
@@ -213,7 +214,7 @@ public final class LegacyLoadingPhaseRunner extends LoadingPhaseRunner {
*/
private LoadingResult doSimpleLoadingPhase(EventHandler eventHandler, EventBus eventBus,
ResolvedTargets<Target> targets, ImmutableSet<Target> testsToRun, boolean keepGoing)
- throws LoadingFailedException {
+ throws InterruptedException, LoadingFailedException {
Stopwatch timer = preLoadingLogging(eventHandler);
ImmutableSet<Target> targetsToLoad = targets.getTargets();
@@ -226,7 +227,7 @@ public final class LegacyLoadingPhaseRunner extends LoadingPhaseRunner {
postLoadingLogging(eventBus, targetsToLoad, expandedResult.getTargets(), timer);
return new LoadingResult(targets.hasError(), expandedResult.hasError(),
- expandedResult.getTargets(), testsToRun);
+ expandedResult.getTargets(), testsToRun, getWorkspaceName(eventHandler));
}
/**
@@ -254,10 +255,9 @@ public final class LegacyLoadingPhaseRunner extends LoadingPhaseRunner {
freeMemoryAfterLoading(callback, pkgLoader.getVisitedPackageNames());
postLoadingLogging(eventBus, baseResult.getTargets(), expandedResult.getTargets(), timer);
- LoadingResult loadingResult = new LoadingResult(targets.hasError(),
+ return new LoadingResult(targets.hasError(),
!baseResult.isSuccesful() || expandedResult.hasError(),
- expandedResult.getTargets(), testsToRun);
- return loadingResult;
+ expandedResult.getTargets(), testsToRun, getWorkspaceName(eventHandler));
}
private Stopwatch preLoadingLogging(EventHandler eventHandler) {
@@ -445,4 +445,14 @@ public final class LegacyLoadingPhaseRunner extends LoadingPhaseRunner {
}
}
}
+
+ private String getWorkspaceName(EventHandler eventHandler)
+ throws InterruptedException, LoadingFailedException {
+ try {
+ return packageManager.getPackage(eventHandler, Label.EXTERNAL_PACKAGE_IDENTIFIER)
+ .getWorkspaceName();
+ } catch (NoSuchPackageException e) {
+ throw new LoadingFailedException("Failed to load //external package", e);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingResult.java b/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingResult.java
index 94f597649e..f78e0afb75 100644
--- a/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingResult.java
+++ b/src/main/java/com/google/devtools/build/lib/pkgcache/LoadingResult.java
@@ -27,14 +27,16 @@ public final class LoadingResult {
private final boolean hasLoadingError;
private final ImmutableSet<Target> targetsToAnalyze;
private final ImmutableSet<Target> testsToRun;
+ private final String workspaceName;
public LoadingResult(boolean hasTargetPatternError, boolean hasLoadingError,
- Collection<Target> targetsToAnalyze, Collection<Target> testsToRun) {
+ Collection<Target> targetsToAnalyze, Collection<Target> testsToRun, String workspaceName) {
this.hasTargetPatternError = hasTargetPatternError;
this.hasLoadingError = hasLoadingError;
this.targetsToAnalyze =
targetsToAnalyze == null ? null : ImmutableSet.copyOf(targetsToAnalyze);
this.testsToRun = testsToRun == null ? null : ImmutableSet.copyOf(testsToRun);
+ this.workspaceName = workspaceName;
}
/** Whether there were errors during target pattern evaluation. */
@@ -56,4 +58,9 @@ public final class LoadingResult {
public Collection<Target> getTestsToRun() {
return testsToRun;
}
+
+ /** The name of the local workspace. */
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
} \ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
index 9608878f34..5215b7370a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
@@ -20,6 +20,7 @@ import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.ResolvedTargets;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.pkgcache.CompileOneDependencyTransformer;
@@ -37,14 +38,12 @@ import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.ValueOrException;
-
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-
import javax.annotation.Nullable;
/**
@@ -56,6 +55,22 @@ final class TargetPatternPhaseFunction implements SkyFunction {
@Override
public TargetPatternPhaseValue compute(SkyKey key, Environment env) {
TargetPatternList options = (TargetPatternList) key.argument();
+ PackageValue packageValue = null;
+ boolean workspaceError = false;
+ try {
+ packageValue = (PackageValue) env.getValueOrThrow(
+ PackageValue.key(Label.EXTERNAL_PACKAGE_IDENTIFIER), NoSuchPackageException.class);
+ } catch (NoSuchPackageException e) {
+ env.getListener().handle(Event.error(e.getMessage()));
+ workspaceError = true;
+ }
+ if (env.valuesMissing()) {
+ return null;
+ }
+ String workspaceName = "";
+ if (!workspaceError) {
+ workspaceName = packageValue.getPackage().getWorkspaceName();
+ }
// Determine targets to build:
ResolvedTargets<Target> targets = getTargetsToBuild(env,
@@ -163,8 +178,8 @@ final class TargetPatternPhaseFunction implements SkyFunction {
Set<Target> testSuiteTargets =
Sets.difference(targets.getTargets(), expandedTargets.getTargets());
return new TargetPatternPhaseValue(expandedTargets.getTargets(), testsToRun, preExpansionError,
- expandedTargets.hasError(), filteredTargets, testFilteredTargets,
- targets.getTargets(), ImmutableSet.copyOf(testSuiteTargets));
+ expandedTargets.hasError() || workspaceError, filteredTargets, testFilteredTargets,
+ targets.getTargets(), ImmutableSet.copyOf(testSuiteTargets), workspaceName);
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
index 0e43191dbc..ca8881386f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseValue.java
@@ -53,11 +53,12 @@ public final class TargetPatternPhaseValue implements SkyValue {
// TODO(ulfjack): Support EventBus event posting in Skyframe, and remove this code again.
private final ImmutableSet<Target> originalTargets;
private final ImmutableSet<Target> testSuiteTargets;
+ private final String workspaceName;
TargetPatternPhaseValue(ImmutableSet<Target> targets, @Nullable ImmutableSet<Target> testsToRun,
boolean hasError, boolean hasPostExpansionError, ImmutableSet<Target> filteredTargets,
ImmutableSet<Target> testFilteredTargets, ImmutableSet<Target> originalTargets,
- ImmutableSet<Target> testSuiteTargets) {
+ ImmutableSet<Target> testSuiteTargets, String workspaceName) {
this.targets = Preconditions.checkNotNull(targets);
this.testsToRun = testsToRun;
this.hasError = hasError;
@@ -66,6 +67,7 @@ public final class TargetPatternPhaseValue implements SkyValue {
this.testFilteredTargets = Preconditions.checkNotNull(testFilteredTargets);
this.originalTargets = Preconditions.checkNotNull(originalTargets);
this.testSuiteTargets = Preconditions.checkNotNull(testSuiteTargets);
+ this.workspaceName = workspaceName;
}
public ImmutableSet<Target> getTargets() {
@@ -101,8 +103,13 @@ public final class TargetPatternPhaseValue implements SkyValue {
return testSuiteTargets;
}
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
public LoadingResult toLoadingResult() {
- return new LoadingResult(hasError(), hasPostExpansionError(), getTargets(), getTestsToRun());
+ return new LoadingResult(
+ hasError(), hasPostExpansionError(), getTargets(), getTestsToRun(), getWorkspaceName());
}
@SuppressWarnings("unused")
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java b/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java
index fec327e8e8..d86d2be261 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/SymlinkForestTest.java
@@ -27,20 +27,19 @@ import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.testutil.ManualClock;
+import com.google.devtools.build.lib.testutil.TestConstants;
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.Symlinks;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-
+import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.IOException;
-
/**
* Tests {@link SymlinkForest}.
*/
@@ -188,7 +187,8 @@ public class SymlinkForestTest {
Path linkRoot = fileSystem.getPath("/linkRoot");
createDirectoryAndParents(linkRoot);
- new SymlinkForest(packageRootMap, linkRoot, "mock-product-name").plantSymlinkForest();
+ new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname")
+ .plantSymlinkForest();
assertLinksTo(linkRoot, rootA, "pkgA");
assertIsDir(linkRoot, "dir1");
@@ -215,7 +215,8 @@ public class SymlinkForestTest {
.put(createPkg(rootX, rootY, "foo"), rootX)
.build();
- new SymlinkForest(packageRootMap, linkRoot, "mock-product-name").plantSymlinkForest();
+ new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname")
+ .plantSymlinkForest();
assertLinksTo(linkRoot, rootX, "file");
}
@@ -239,7 +240,8 @@ public class SymlinkForestTest {
.put(createPkg(outputBase, "w", ""), outputBase)
.build();
- new SymlinkForest(packageRootMap, linkRoot, "mock-product-name").plantSymlinkForest();
+ new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname")
+ .plantSymlinkForest();
assertFalse(linkRoot.getRelative(Label.EXTERNAL_PATH_PREFIX + "/y/file").exists());
assertLinksTo(
linkRoot.getRelative(Label.EXTERNAL_PATH_PREFIX + "/y/w"), rootY.getRelative("w"));
@@ -261,7 +263,22 @@ public class SymlinkForestTest {
.put(Label.EXTERNAL_PACKAGE_IDENTIFIER, root)
.build();
- new SymlinkForest(packageRootMap, linkRoot, "mock-product-name").plantSymlinkForest();
+ new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname")
+ .plantSymlinkForest();
assertThat(linkRoot.getRelative(Label.EXTERNAL_PATH_PREFIX).exists()).isFalse();
}
+
+ @Test
+ public void testWorkspaceName() throws Exception {
+ Path root = fileSystem.getPath("/src");
+ ImmutableMap<PackageIdentifier, Path> packageRootMap =
+ ImmutableMap.<PackageIdentifier, Path>builder()
+ // Remote repo without top-level package.
+ .put(createPkg(root, "y", "w"), root)
+ .build();
+
+ new SymlinkForest(packageRootMap, linkRoot, TestConstants.PRODUCT_NAME, "wsname")
+ .plantSymlinkForest();
+ assertThat(linkRoot.getRelative("../wsname").exists()).isTrue();
+ }
}
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index a3d9751b3a..5ed4d00481 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -355,6 +355,13 @@ sh_test(
data = [":test-deps"],
)
+sh_test(
+ name = "execroot_test",
+ size = "small",
+ srcs = ["execroot_test.sh"],
+ data = [":test-deps"],
+)
+
test_suite(
name = "all_tests",
visibility = ["//visibility:public"],
diff --git a/src/test/shell/bazel/execroot_test.sh b/src/test/shell/bazel/execroot_test.sh
new file mode 100755
index 0000000000..0a97fa6cb0
--- /dev/null
+++ b/src/test/shell/bazel/execroot_test.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+set -eu
+
+# Load test environment
+source $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test-setup.sh \
+ || { echo "test-setup.sh not found!" >&2; exit 1; }
+
+function test_execroot_structure() {
+ ws_name="dooby_dooby_doo"
+ cat > WORKSPACE <<EOF
+workspace(name = "$ws_name")
+EOF
+
+ mkdir dir
+ cat > dir/BUILD <<'EOF'
+genrule(
+ name = "use-srcs",
+ srcs = ["BUILD"],
+ cmd = "cp $< $@",
+ outs = ["used-srcs"],
+)
+EOF
+
+ bazel build -s //dir:use-srcs &> $TEST_log || fail "expected success"
+ test -e "$(bazel info execution_root)/../${ws_name}"
+}
+
+run_suite "execution root tests"