Add output_root_symlinks attribute to ninja_build rule
output_root_symlinks allow to list output paths under output_root, that should be treated as symlink artifacts.
In combination with --experimental_allow_unresolved_symlinks flag, this allows Ninja actions to create symlinks, not pointing to the existing file.
Closes #10892.
PiperOrigin-RevId: 298821497
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuild.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuild.java
index f52aae9..89b84b9 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuild.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuild.java
@@ -71,7 +71,8 @@
graphProvider.getWorkingDirectory(),
createSrcsMap(ruleContext),
depsMapBuilder.build(),
- symlinksMapBuilder.build());
+ symlinksMapBuilder.build(),
+ graphProvider.getOutputRootSymlinks());
if (ruleContext.hasErrors()) {
return null;
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraph.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraph.java
index 42e8d8e..7c75e51 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraph.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraph.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.bazel.rules.ninja.actions;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
@@ -50,6 +51,7 @@
import com.google.devtools.build.skyframe.SkyKey;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
@@ -81,6 +83,8 @@
PathFragment.create(ruleContext.attributes().get("working_directory", Type.STRING));
List<String> outputRootInputs =
ruleContext.attributes().get("output_root_inputs", Type.STRING_LIST);
+ List<String> outputRootSymlinks =
+ ruleContext.attributes().get("output_root_symlinks", Type.STRING_LIST);
Environment env = ruleContext.getAnalysisEnvironment().getSkyframeEnv();
establishDependencyOnNinjaFiles(env, mainArtifact, ninjaSrcs);
@@ -98,7 +102,8 @@
workingDirectory,
ImmutableSortedMap.of(),
ImmutableSortedMap.of(),
- ImmutableSortedMap.of());
+ ImmutableSortedMap.of(),
+ ImmutableSortedSet.of());
if (ruleContext.hasErrors()) {
return null;
}
@@ -126,7 +131,11 @@
outputRoot,
workingDirectory,
targetsPreparer.getUsualTargets(),
- targetsPreparer.getPhonyTargetsMap());
+ targetsPreparer.getPhonyTargetsMap(),
+ outputRootSymlinks.stream()
+ .map(PathFragment::create)
+ .collect(
+ toImmutableSortedSet(Comparator.comparing(PathFragment::getPathString))));
NestedSet<Artifact> filesToBuild =
createSymlinkActions(
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java
index 4c99e69..3cd4e3d 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphArtifactsHelper.java
@@ -17,6 +17,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact;
import com.google.devtools.build.lib.actions.ArtifactRoot;
@@ -42,6 +43,7 @@
private final ImmutableSortedMap<PathFragment, Artifact> depsNameToArtifact;
private final ImmutableSortedMap<PathFragment, Artifact> symlinkPathToArtifact;
+ private final ImmutableSortedSet<PathFragment> outputRootSymlinks;
private final ImmutableSortedMap<PathFragment, Artifact> srcsMap;
/**
@@ -54,7 +56,9 @@
* @param srcsMap mapping between the path fragment and artifact for the files passed in 'srcs'
* attribute
* @param depsNameToArtifact mapping between the path fragment in the Ninja file and prebuilt
- * @param symlinkPathToArtifact
+ * @param symlinkPathToArtifact mapping of paths to artifacts for input symlinks under output_root
+ * @param outputRootSymlinks list of output paths for which symlink artifacts should be created,
+ * paths are relative to the output_root.
*/
NinjaGraphArtifactsHelper(
RuleContext ruleContext,
@@ -62,13 +66,15 @@
PathFragment workingDirectory,
ImmutableSortedMap<PathFragment, Artifact> srcsMap,
ImmutableSortedMap<PathFragment, Artifact> depsNameToArtifact,
- ImmutableSortedMap<PathFragment, Artifact> symlinkPathToArtifact) {
+ ImmutableSortedMap<PathFragment, Artifact> symlinkPathToArtifact,
+ ImmutableSortedSet<PathFragment> outputRootSymlinks) {
this.ruleContext = ruleContext;
this.outputRootPath = outputRootPath;
this.workingDirectory = workingDirectory;
this.srcsMap = srcsMap;
this.depsNameToArtifact = depsNameToArtifact;
this.symlinkPathToArtifact = symlinkPathToArtifact;
+ this.outputRootSymlinks = outputRootSymlinks;
Path execRoot =
Preconditions.checkNotNull(ruleContext.getConfiguration())
.getDirectories()
@@ -87,10 +93,15 @@
+ " path '%s' is not allowed.",
pathRelativeToWorkingDirectory));
}
- DerivedArtifact derivedArtifact =
- ruleContext.getDerivedArtifact(
- pathRelativeToWorkspaceRoot.relativeTo(outputRootPath), derivedOutputRoot);
- return derivedArtifact;
+ // If the path was declared as output symlink, create a symlink artifact.
+ if (outputRootSymlinks.contains(pathRelativeToWorkspaceRoot.relativeTo(outputRootPath))) {
+ return ruleContext
+ .getAnalysisEnvironment()
+ .getSymlinkArtifact(
+ pathRelativeToWorkspaceRoot.relativeTo(outputRootPath), derivedOutputRoot);
+ }
+ return ruleContext.getDerivedArtifact(
+ pathRelativeToWorkspaceRoot.relativeTo(outputRootPath), derivedOutputRoot);
}
Artifact getInputArtifact(PathFragment pathRelativeToWorkingDirectory)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphProvider.java
index 22ab3ae..7bb50bb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphProvider.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.bazel.rules.ninja.actions;
import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaTarget;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
@@ -30,16 +31,19 @@
private final PathFragment workingDirectory;
private final ImmutableSortedMap<PathFragment, NinjaTarget> usualTargets;
private final ImmutableSortedMap<PathFragment, PhonyTarget> phonyTargetsMap;
+ private final ImmutableSortedSet<PathFragment> outputRootSymlinks;
public NinjaGraphProvider(
PathFragment outputRoot,
PathFragment workingDirectory,
ImmutableSortedMap<PathFragment, NinjaTarget> usualTargets,
- ImmutableSortedMap<PathFragment, PhonyTarget> phonyTargetsMap) {
+ ImmutableSortedMap<PathFragment, PhonyTarget> phonyTargetsMap,
+ ImmutableSortedSet<PathFragment> outputRootSymlinks) {
this.outputRoot = outputRoot;
this.workingDirectory = workingDirectory;
this.usualTargets = usualTargets;
this.phonyTargetsMap = phonyTargetsMap;
+ this.outputRootSymlinks = outputRootSymlinks;
}
public PathFragment getOutputRoot() {
@@ -57,4 +61,9 @@
public ImmutableSortedMap<PathFragment, PhonyTarget> getPhonyTargetsMap() {
return phonyTargetsMap;
}
+
+ /** Output paths under output_root, that should be treated as symlink artifacts. */
+ public ImmutableSortedSet<PathFragment> getOutputRootSymlinks() {
+ return outputRootSymlinks;
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphRule.java
index b95fac2..29d9b23 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaGraphRule.java
@@ -78,6 +78,14 @@
+ " <execroot>/<output_root> will be a separate directory, not a"
+ " symlink.</p>"))
.add(
+ attr("output_root_symlinks", STRING_LIST)
+ .value(ImmutableList.of())
+ .setDoc(
+ "<p>Output paths under output_root, that should be treated as symlink"
+ + " artifacts.</p><p>In combination with"
+ + " --experimental_allow_unresolved_symlinks flag, this allows Ninja"
+ + " actions to create symlinks, not pointing to the existing file.</p>"))
+ .add(
attr("working_directory", STRING)
.value("")
.setDoc(
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaBuildTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaBuildTest.java
index c597f65..5fa0b1f 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaBuildTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaBuildTest.java
@@ -366,4 +366,43 @@
assertThat(commandLines.get(0).commandLine.toString())
.endsWith("cd build_config && executable -d out_file.d ../input > out_file");
}
+
+ @Test
+ public void testCreateOutputSymlinkArtifacts() throws Exception {
+ rewriteWorkspace(
+ "workspace(name = 'test')",
+ "dont_symlink_directories_in_execroot(paths = ['build_config'])");
+
+ scratch.file(
+ "build_config/build.ninja",
+ "rule symlink_rule",
+ " command = ln -s fictive-file ${out}",
+ "build dangling_symlink: symlink_rule");
+
+ ConfiguredTarget configuredTarget =
+ scratchConfiguredTarget(
+ "",
+ "ninja_target",
+ "ninja_graph(name = 'graph', output_root = 'build_config',",
+ " working_directory = 'build_config',",
+ " main = 'build_config/build.ninja',",
+ " output_root_symlinks = ['dangling_symlink'])",
+ "ninja_build(name = 'ninja_target', ninja_graph = 'graph',",
+ " output_groups= {'main': ['dangling_symlink']})");
+ assertThat(configuredTarget).isInstanceOf(RuleConfiguredTarget.class);
+ RuleConfiguredTarget ninjaConfiguredTarget = (RuleConfiguredTarget) configuredTarget;
+ ImmutableList<ActionAnalysisMetadata> actions = ninjaConfiguredTarget.getActions();
+ assertThat(actions).hasSize(1);
+
+ ActionAnalysisMetadata action = Iterables.getOnlyElement(actions);
+ Artifact primaryOutput = action.getPrimaryOutput();
+ assertThat(primaryOutput.isSymlink()).isTrue();
+ assertThat(action).isInstanceOf(NinjaAction.class);
+
+ List<CommandLineAndParamFileInfo> commandLines =
+ ((NinjaAction) action).getCommandLines().getCommandLines();
+ assertThat(commandLines).hasSize(1);
+ assertThat(commandLines.get(0).commandLine.toString())
+ .endsWith("cd build_config && ln -s fictive-file dangling_symlink");
+ }
}