Replace Artifact's exec_path with PathFragments.

There are many common substrings in exec_paths (e.g. bazel-out, ...). By
preserving the PathFragment structure in the output, we can avoid this
duplication.

This is a breaking change, and consumers of analysis_v2.proto should change their
implementation to use PathFragment instead of exec_path.

RELNOTES: None
PiperOrigin-RevId: 281936154
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownArtifacts.java b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownArtifacts.java
index 8b4989f..6d78d55 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownArtifacts.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownArtifacts.java
@@ -20,19 +20,25 @@
 /** Cache for Artifacts in the action graph. */
 public class KnownArtifacts extends BaseCache<Artifact, AnalysisProtosV2.Artifact> {
 
+  private final KnownPathFragments knownPathFragments;
+
   KnownArtifacts(ActionGraphContainer.Builder actionGraphBuilder) {
     super(actionGraphBuilder);
+    knownPathFragments = new KnownPathFragments(actionGraphBuilder);
   }
 
   @Override
   AnalysisProtosV2.Artifact createProto(Artifact artifact, Long id) {
-    return AnalysisProtosV2.Artifact.newBuilder()
-        .setId(id)
-        .setExecPath(artifact.getExecPathString())
-        .setIsTreeArtifact(artifact.isTreeArtifact())
-        .build();
+    AnalysisProtosV2.Artifact.Builder artifactProtoBuilder =
+        AnalysisProtosV2.Artifact.newBuilder()
+            .setId(id)
+            .setIsTreeArtifact(artifact.isTreeArtifact());
+
+    Long pathFragmentId = knownPathFragments.dataToId(artifact.getExecPath());
+    return artifactProtoBuilder.setPathFragmentId(pathFragmentId).build();
   }
 
+
   @Override
   void addToActionGraphBuilder(AnalysisProtosV2.Artifact artifactProto) {
     actionGraphBuilder.addArtifacts(artifactProto);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownPathFragments.java b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownPathFragments.java
new file mode 100644
index 0000000..0f918a3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/actiongraph/v2/KnownPathFragments.java
@@ -0,0 +1,50 @@
+// 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.actiongraph.v2;
+
+import com.google.devtools.build.lib.analysis.AnalysisProtosV2;
+import com.google.devtools.build.lib.analysis.AnalysisProtosV2.ActionGraphContainer;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/** Cache for {@link PathFragment} in the action graph. */
+public class KnownPathFragments extends BaseCache<PathFragment, AnalysisProtosV2.PathFragment> {
+  KnownPathFragments(ActionGraphContainer.Builder actionGraphBuilder) {
+    super(actionGraphBuilder);
+  }
+
+  @Override
+  AnalysisProtosV2.PathFragment createProto(PathFragment pathFragment, Long id) {
+    AnalysisProtosV2.PathFragment.Builder pathFragmentProtoBuilder =
+        AnalysisProtosV2.PathFragment.newBuilder().setId(id).setLabel(pathFragment.getBaseName());
+
+    // Recursively create the ancestor path fragments.
+    // If pathFragment has no parent, leave parentId blank and avoid calling dataToId
+    // to prevent the cache from being polluted with a null entry.
+    if (hasParent(pathFragment)) {
+      pathFragmentProtoBuilder.setParentId(dataToId(pathFragment.getParentDirectory()));
+    }
+
+    return pathFragmentProtoBuilder.build();
+  }
+
+  @Override
+  void addToActionGraphBuilder(AnalysisProtosV2.PathFragment pathFragmentProto) {
+    actionGraphBuilder.addPathFragments(pathFragmentProto);
+  }
+
+  private static boolean hasParent(PathFragment pathFragment) {
+    return pathFragment.getParentDirectory() != null
+        && !pathFragment.getParentDirectory().getBaseName().isEmpty();
+  }
+}
diff --git a/src/main/protobuf/analysis_v2.proto b/src/main/protobuf/analysis_v2.proto
index eaba2a6..868d33d 100644
--- a/src/main/protobuf/analysis_v2.proto
+++ b/src/main/protobuf/analysis_v2.proto
@@ -30,6 +30,7 @@
   repeated Configuration configuration = 5;
   repeated AspectDescriptor aspect_descriptors = 6;
   repeated RuleClass rule_classes = 7;
+  repeated PathFragment path_fragments = 8;
 }
 
 // Represents a single artifact, whether it's a source file or a derived output
@@ -39,8 +40,9 @@
   // particular dump of the analysis.
   uint64 id = 1;
 
-  // The relative path of the file within the execution root.
-  string exec_path = 2;
+  // The id of the PathFragment that represents the relative path of the file
+  // within the execution root.
+  uint64 path_fragment_id = 2;
 
   // True iff the artifact is a tree artifact, i.e. the above exec_path refers
   // a directory.
@@ -198,3 +200,15 @@
   // Each argument corresponds to a line in the param file.
   repeated string arguments = 2;
 }
+
+// The path fragment that makes up a full path.
+message PathFragment {
+  // Identifier for this path fragment.
+  uint64 id = 1;
+
+  // The label of the section in the path.
+  string label = 2;
+
+  // The id of the parent path fragment.
+  uint64 parent_id = 3;
+}
diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh
index 5952164..ba649b8 100755
--- a/src/test/shell/integration/aquery_test.sh
+++ b/src/test/shell/integration/aquery_test.sh
@@ -1090,7 +1090,6 @@
     cmd = "echo unused > $(OUTS)",
 )
 EOF
-  echo "hello aquery" > "$pkg/in.txt"
   bazel aquery --incompatible_proto_output_v2 --output=proto "//$pkg:bar" \
     || fail "Expected success"
 
@@ -1100,8 +1099,9 @@
   # Verify than ids come in integers instead of strings.
   assert_contains "id: 1" output
   assert_not_contains "id: \"1\"" output
-  assert_contains "exec_path: \"$pkg/dummy.txt\"" output
-  assert_contains "nemonic: \"Genrule\"" output
+  assert_contains "path_fragments {" output
+  assert_contains "label: \"dummy.txt\"" output
+  assert_contains "mnemonic: \"Genrule\"" output
   assert_contains "mnemonic: \".*-fastbuild\"" output
   assert_contains "echo unused" output
 }