Create source artifacts in the ninja_build rule instead of requiring them to be explicitly listed in the ninja_build.srcs attribute.

Pros:

* Makes it unnecessary to duplicate information in BUILD and .ninja files
* Absolves Bazel from tracking gazillions of unnecessary files

Cons:

* Can be problematic if multiple --package_path entries exist (e.g. one can have file a/b/c in one root while there is a/b/BUILD in another)
* Invalidates the invariant that "bazel query 'deps(<target>)'" returns all files that can possibly be needed to build that target

That's why Ninja support is still in experimental mode.

RELNOTES: None.
PiperOrigin-RevId: 298863427
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
index 1c1cf48..675dcb0 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
@@ -29,6 +29,7 @@
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.skyframe.SkyFunction;
 
 /**
@@ -96,6 +97,23 @@
   SpecialArtifact getSymlinkArtifact(PathFragment rootRelativePath, ArtifactRoot root);
 
   /**
+   * Creates a source artifact.
+   *
+   * <p>Do <b>NOT</b> use this unless you have a very good reason to do so and have consulted with
+   * someone knowledgeable. Source artifacts should be only created through {@link
+   * com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget} by {@link
+   * ConfiguredTargetFactory}.
+   *
+   * <p>This method should only be used when that's not an option. Currently, the only known use
+   * case is the {@code ninja_build} rule, which, if it couldn't create source artifacts, would have
+   * to have every source artifact that Ninja actions use enumerated in its {@code srcs} attribute.
+   *
+   * <p>If you use this erroneously, inconsistencies can occur, for example, creating a source
+   * artifact with the wrong package path entry or in the wrong package.
+   */
+  Artifact getSourceArtifactForNinjaBuild(PathFragment execpath, Root root);
+
+  /**
    * Returns the artifact for the derived file {@code rootRelativePath}, creating it if necessary,
    * and setting the root of that artifact to {@code root}. The artifact will represent the output
    * directory of a {@code Fileset}.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
index 91d537b..80648ac 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
@@ -38,6 +38,7 @@
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.skyframe.SkyFunction;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -312,6 +313,11 @@
   }
 
   @Override
+  public Artifact getSourceArtifactForNinjaBuild(PathFragment execPath, Root root) {
+    return artifactFactory.getSourceArtifact(execPath, root, owner);
+  }
+
+  @Override
   public Artifact.DerivedArtifact getFilesetArtifact(
       PathFragment rootRelativePath, ArtifactRoot root) {
     Preconditions.checkState(enabled);
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 89b84b9..fe7ddea 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
@@ -15,7 +15,6 @@
 package com.google.devtools.build.lib.bazel.rules.ninja.actions;
 
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.Maps;
@@ -69,7 +68,6 @@
             ruleContext,
             graphProvider.getOutputRoot(),
             graphProvider.getWorkingDirectory(),
-            createSrcsMap(ruleContext),
             depsMapBuilder.build(),
             symlinksMapBuilder.build(),
             graphProvider.getOutputRootSymlinks());
@@ -162,14 +160,6 @@
     return nestedSetBuilder.build();
   }
 
-  private static ImmutableSortedMap<PathFragment, Artifact> createSrcsMap(RuleContext ruleContext) {
-    ImmutableList<Artifact> srcs = ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list();
-    ImmutableSortedMap.Builder<PathFragment, Artifact> inputsMapBuilder =
-        ImmutableSortedMap.naturalOrder();
-    srcs.forEach(a -> inputsMapBuilder.put(a.getRootRelativePath(), a));
-    return inputsMapBuilder.build();
-  }
-
   private static void createDepsMap(
       RuleContext ruleContext,
       PathFragment workingDirectory,
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuildRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuildRule.java
index 76e727b..805e14f 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuildRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/actions/NinjaBuildRule.java
@@ -16,7 +16,6 @@
 
 import static com.google.devtools.build.lib.packages.Attribute.attr;
 import static com.google.devtools.build.lib.packages.BuildType.LABEL;
-import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
@@ -40,6 +39,10 @@
  * Bazel will determine that those are duplicates and only execute each action once. Future
  * improvements are planned to avoid creation of duplicate actions, probably with the help of some
  * coordinating registry structure.
+ *
+ * <p>Currently all input files of the Ninja graph must be in a subdirectory of the package the
+ * {@code ninja_build} rule is in. It's currently okay if they are in a subpackage, although that
+ * may change later. For best results, put this rule in the top-level BUILD file.
  */
 public class NinjaBuildRule implements RuleDefinition {
   @Override
@@ -51,10 +54,6 @@
                 .allowedRuleClasses("ninja_graph")
                 .setDoc("ninja_graph that parses all Ninja files that compose a graph of actions."))
         .add(
-            attr("srcs", LABEL_LIST)
-                .allowedFileTypes(FileTypeSet.ANY_FILE)
-                .setDoc("Source files requested by Ninja graph actions."))
-        .add(
             attr("deps_mapping", BuildType.LABEL_DICT_UNARY)
                 .allowedFileTypes(FileTypeSet.ANY_FILE)
                 .setDoc(
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 7c75e51..6dfb5c3 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
@@ -102,7 +102,6 @@
             workingDirectory,
             ImmutableSortedMap.of(),
             ImmutableSortedMap.of(),
-            ImmutableSortedMap.of(),
             ImmutableSortedSet.of());
     if (ruleContext.hasErrors()) {
       return null;
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 3cd4e3d..377e1f2 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
@@ -44,7 +44,6 @@
   private final ImmutableSortedMap<PathFragment, Artifact> depsNameToArtifact;
   private final ImmutableSortedMap<PathFragment, Artifact> symlinkPathToArtifact;
   private final ImmutableSortedSet<PathFragment> outputRootSymlinks;
-  private final ImmutableSortedMap<PathFragment, Artifact> srcsMap;
 
   /**
    * Constructor
@@ -53,8 +52,6 @@
    * @param outputRootPath name of output directory for Ninja actions under execroot
    * @param workingDirectory relative path under execroot, the root for interpreting all paths in
    *     Ninja file
-   * @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 mapping of paths to artifacts for input symlinks under output_root
    * @param outputRootSymlinks list of output paths for which symlink artifacts should be created,
@@ -64,14 +61,12 @@
       RuleContext ruleContext,
       PathFragment outputRootPath,
       PathFragment workingDirectory,
-      ImmutableSortedMap<PathFragment, Artifact> srcsMap,
       ImmutableSortedMap<PathFragment, Artifact> depsNameToArtifact,
       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;
@@ -84,9 +79,9 @@
 
   DerivedArtifact createOutputArtifact(PathFragment pathRelativeToWorkingDirectory)
       throws GenericParsingException {
-    PathFragment pathRelativeToWorkspaceRoot =
-        workingDirectory.getRelative(pathRelativeToWorkingDirectory);
-    if (!pathRelativeToWorkspaceRoot.startsWith(outputRootPath)) {
+    PathFragment execPath = workingDirectory.getRelative(pathRelativeToWorkingDirectory);
+
+    if (!execPath.startsWith(outputRootPath)) {
       throw new GenericParsingException(
           String.format(
               "Ninja actions are allowed to create outputs only under output_root,"
@@ -94,41 +89,47 @@
               pathRelativeToWorkingDirectory));
     }
     // If the path was declared as output symlink, create a symlink artifact.
-    if (outputRootSymlinks.contains(pathRelativeToWorkspaceRoot.relativeTo(outputRootPath))) {
+    if (outputRootSymlinks.contains(execPath.relativeTo(outputRootPath))) {
       return ruleContext
           .getAnalysisEnvironment()
-          .getSymlinkArtifact(
-              pathRelativeToWorkspaceRoot.relativeTo(outputRootPath), derivedOutputRoot);
+          .getSymlinkArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot);
     }
-    return ruleContext.getDerivedArtifact(
-        pathRelativeToWorkspaceRoot.relativeTo(outputRootPath), derivedOutputRoot);
+    return ruleContext.getDerivedArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot);
   }
 
-  Artifact getInputArtifact(PathFragment pathRelativeToWorkingDirectory)
-      throws GenericParsingException {
-    Preconditions.checkNotNull(srcsMap);
-    PathFragment pathRelativeToWorkspaceRoot =
-        workingDirectory.getRelative(pathRelativeToWorkingDirectory);
-    Artifact asInput = srcsMap.get(pathRelativeToWorkspaceRoot);
-    Artifact depsMappingArtifact = depsNameToArtifact.get(pathRelativeToWorkingDirectory);
-    Artifact symlinkMappingArtifact = symlinkPathToArtifact.get(pathRelativeToWorkingDirectory);
-    // Symlinked artifact is by definition outside of sources, in the output directory.
-    if (asInput != null && depsMappingArtifact != null) {
+  Artifact getInputArtifact(PathFragment workingDirectoryPath) throws GenericParsingException {
+    if (depsNameToArtifact.containsKey(workingDirectoryPath)) {
+      return depsNameToArtifact.get(workingDirectoryPath);
+    }
+
+    if (symlinkPathToArtifact.containsKey(workingDirectoryPath)) {
+      return symlinkPathToArtifact.get(workingDirectoryPath);
+    }
+
+    PathFragment execPath = workingDirectory.getRelative(workingDirectoryPath);
+    if (execPath.startsWith(outputRootPath)) {
+      // In the output directory, so it must be a derived artifact.
+      return ruleContext.getDerivedArtifact(execPath.relativeTo(outputRootPath), derivedOutputRoot);
+    }
+
+    if (!execPath.startsWith(ruleContext.getPackageDirectory())) {
       throw new GenericParsingException(
           String.format(
-              "Source file '%s' is passed both in 'srcs' " + "and 'deps_mapping' attributes.",
-              pathRelativeToWorkingDirectory));
+              "Source artifact '%s' is not under the package of the ninja_build rule", execPath));
     }
-    if (asInput != null) {
-      return asInput;
-    }
-    if (depsMappingArtifact != null) {
-      return depsMappingArtifact;
-    }
-    if (symlinkMappingArtifact != null) {
-      return symlinkMappingArtifact;
-    }
-    return createOutputArtifact(pathRelativeToWorkingDirectory);
+
+    // Not a derived artifact. Create a corresponding source artifact. This isn't really great
+    // because we have no guarantee that the artifact is not under a different package which
+    // invalidates the guarantee of "bazel query" that the dependencies reported for a target are a
+    // superset of all possible targets that are needed to build it, worse yet, there isn't even a
+    // guarantee that there isn't a package on a different package path in between.
+    //
+    // TODO(lberki): Check whether there is a package in between from another package path entry.
+    // We probably can't prohibit packages in between, though. Schade.
+    return ruleContext
+        .getAnalysisEnvironment()
+        .getSourceArtifactForNinjaBuild(
+            execPath, ruleContext.getRule().getPackage().getSourceRoot());
   }
 
   public Artifact getDepsMappingArtifact(PathFragment fragment) {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
index da62d1f..3172d4a 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
@@ -53,6 +53,7 @@
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import java.io.IOException;
@@ -147,6 +148,11 @@
     }
 
     @Override
+    public Artifact getSourceArtifactForNinjaBuild(PathFragment execPath, Root root) {
+      return original.getSourceArtifactForNinjaBuild(execPath, root);
+    }
+
+    @Override
     public Artifact getFilesetArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
       return original.getFilesetArtifact(rootRelativePath, root);
     }
@@ -345,6 +351,11 @@
     }
 
     @Override
+    public Artifact getSourceArtifactForNinjaBuild(PathFragment execPath, Root root) {
+      return null;
+    }
+
+    @Override
     public ExtendedEventHandler getEventHandler() {
       return null;
     }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 12e34e6..e14b09f 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -1996,6 +1996,11 @@
     }
 
     @Override
+    public Artifact getSourceArtifactForNinjaBuild(PathFragment execPath, Root root) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public ExtendedEventHandler getEventHandler() {
       return reporter;
     }
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 5fa0b1f..787b6f5 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
@@ -37,7 +37,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Test for {@link NinjaBuild} configured target factory. */
+/** Test for the {@code NinjaBuild} configured target factory. */
 @RunWith(JUnit4.class)
 public class NinjaBuildTest extends BuildViewTestCase {
 
@@ -56,6 +56,23 @@
   }
 
   @Test
+  public void testSourceFileNotInSubtree() throws Exception {
+    rewriteWorkspace("dont_symlink_directories_in_execroot(paths=['out'])");
+
+    scratch.file("a/n.ninja", "rule cp", " command = cp $in $out", "build out/o: cp subdir/i");
+
+    scratch.file(
+        "a/BUILD",
+        "ninja_graph(name='graph', output_root='out', main='n.ninja')",
+        "ninja_build(name='build', ninja_graph=':graph', output_groups={'o': ['out/o']})");
+
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//a:build");
+    assertContainsEvent(
+        "Source artifact 'subdir/i' is not under the package of the ninja_build rule");
+  }
+
+  @Test
   public void testNinjaBuildRule() throws Exception {
     rewriteWorkspace(
         "workspace(name = 'test')",
@@ -351,7 +368,6 @@
             " working_directory = 'build_config',",
             " main = 'build_config/build.ninja')",
             "ninja_build(name = 'ninja_target', ninja_graph = 'graph',",
-            " srcs = ['input'],",
             " output_groups= {'main': ['out_file']})");
     assertThat(configuredTarget).isInstanceOf(RuleConfiguredTarget.class);
     RuleConfiguredTarget ninjaConfiguredTarget = (RuleConfiguredTarget) configuredTarget;
diff --git a/src/test/java/com/google/devtools/build/lib/blackbox/tests/NinjaBlackBoxTest.java b/src/test/java/com/google/devtools/build/lib/blackbox/tests/NinjaBlackBoxTest.java
index 6162276..a8a415b 100644
--- a/src/test/java/com/google/devtools/build/lib/blackbox/tests/NinjaBlackBoxTest.java
+++ b/src/test/java/com/google/devtools/build/lib/blackbox/tests/NinjaBlackBoxTest.java
@@ -133,33 +133,6 @@
   }
 
   @Test
-  public void testSourceFileIsMissing() throws Exception {
-    context().write("input.txt", "World");
-    context()
-        .write(
-            "build_dir/build.ninja",
-            "rule echo",
-            "  command = echo \"Hello $$(cat ${in})!\" > ${out}",
-            "build hello.txt: echo ../input.txt");
-    context()
-        .write(
-            "BUILD",
-            "ninja_graph(name = 'graph', output_root = 'build_dir',",
-            " working_directory = 'build_dir',",
-            " main = 'build_dir/build.ninja')",
-            "ninja_build(name = 'ninja_target', ninja_graph = 'graph',",
-            " output_groups = {'group': ['hello.txt']})");
-
-    BuilderRunner bazel = context().bazel().withFlags("--experimental_ninja_actions");
-    ProcessResult result = bazel.shouldFail().build("//:ninja_target");
-    assertThat(result.errString())
-        .contains(
-            "in ninja_build rule //:ninja_target: Ninja actions are allowed to create outputs only "
-                + "under output_root, path '../input.txt' is not allowed.");
-    assertThat(result.errString()).contains("FAILED: Build did NOT complete successfully");
-  }
-
-  @Test
   public void testSourceFileIsMissingUnderOutputRoot() throws Exception {
     context().write("input.txt", "World");
     context()
@@ -625,7 +598,6 @@
             " working_directory = 'build_dir',",
             " main = 'build_dir/build.ninja')",
             "ninja_build(name = 'ninja_target', ninja_graph = 'graph',",
-            "  srcs = [\":input.txt\"],",
             " output_groups= {'main': ['first.txt']})");
 
     BuilderRunner bazel = context().bazel().withFlags("--experimental_ninja_actions");