Add --source-path flag to AAPT2 compile

This added flag uses a given value to replace the default absolute resource
file path in the compiled resource file. This allows for relative file
paths to be used instead of absolute file paths.

Test: Compile_test.cpp
Bug: 159611599
Change-Id: I5f1c99cf40e5d31fc54cda819c91640285986d2a
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 3268653..ff54fcc 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -75,8 +75,10 @@
 };
 
 // Resource file paths are expected to look like: [--/res/]type[-config]/name
-static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep,
-                                                       std::string* out_error) {
+static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
+                                                       const char dir_sep,
+                                                       std::string* out_error,
+                                                       const CompileOptions& options) {
   std::vector<std::string> parts = util::Split(path, dir_sep);
   if (parts.size() < 2) {
     if (out_error) *out_error = "bad resource path";
@@ -121,7 +123,11 @@
     }
   }
 
-  return ResourcePathData{Source(path),          dir_str.to_string(),    name.to_string(),
+  const Source res_path = options.source_path
+      ? StringPiece(options.source_path.value())
+      : StringPiece(path);
+
+  return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
                           extension.to_string(), config_str.to_string(), config};
 }
 
@@ -667,7 +673,8 @@
     // Extract resource type information from the full path
     std::string err_str;
     ResourcePathData path_data;
-    if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) {
+    if (auto maybe_path_data = ExtractResourcePathData(
+        path, inputs->GetDirSeparator(), &err_str, options)) {
       path_data = maybe_path_data.value();
     } else {
       context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str);
@@ -747,6 +754,11 @@
     context.GetDiagnostics()->Error(DiagMessage()
                                       << "only one of --dir and --zip can be specified");
     return 1;
+  } else if ((options_.res_dir || options_.res_zip) &&
+              options_.source_path && args.size() > 1) {
+      context.GetDiagnostics()->Error(DiagMessage(kPath)
+      << "Cannot use an overriding source path with multiple files.");
+      return 1;
   } else if (options_.res_dir) {
     if (!args.empty()) {
       context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 1752a1a..1bc1f66 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -28,6 +28,7 @@
 
 struct CompileOptions {
   std::string output_path;
+  Maybe<std::string> source_path;
   Maybe<std::string> res_dir;
   Maybe<std::string> res_zip;
   Maybe<std::string> generate_text_symbols_path;
@@ -69,6 +70,9 @@
     AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
     AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.",
                     &trace_folder_);
+    AddOptionalFlag("--source-path",
+                      "Sets the compiled resource file source file path to the given string.",
+                      &options_.source_path);
   }
 
   int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index fb786a3..0aab94d3 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -24,6 +24,7 @@
 #include "io/ZipArchive.h"
 #include "java/AnnotationProcessor.h"
 #include "test/Test.h"
+#include "format/proto/ProtoDeserialize.h"
 
 namespace aapt {
 
@@ -253,4 +254,90 @@
   AssertTranslations(this, "donottranslate_foo", expected_not_translatable);
 }
 
+TEST_F(CompilerTest, RelativePathTest) {
+  StdErrDiagnostics diag;
+  const std::string res_path = BuildPath(
+      {android::base::Dirname(android::base::GetExecutablePath()),
+       "integration-tests", "CompileTest", "res"});
+
+  const std::string path_values_colors = GetTestPath("values/colors.xml");
+  WriteFile(path_values_colors, "<resources>"
+                   "<color name=\"color_one\">#008577</color>"
+                   "</resources>");
+
+  const std::string path_layout_layout_one = GetTestPath("layout/layout_one.xml");
+  WriteFile(path_layout_layout_one, "<LinearLayout "
+                   "xmlns:android=\"http://schemas.android.com/apk/res/android\">"
+                   "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>"
+                   "</LinearLayout>");
+
+  const std::string compiled_files_dir  = BuildPath(
+      {android::base::Dirname(android::base::GetExecutablePath()),
+       "integration-tests", "CompileTest", "compiled"});
+  CHECK(file::mkdirs(compiled_files_dir.data()));
+
+  const std::string path_values_colors_out =
+      BuildPath({compiled_files_dir,"values_colors.arsc.flat"});
+  const std::string path_layout_layout_one_out =
+      BuildPath({compiled_files_dir, "layout_layout_one.flat"});
+  ::android::base::utf8::unlink(path_values_colors_out.c_str());
+  ::android::base::utf8::unlink(path_layout_layout_one_out.c_str());
+  const std::string apk_path = BuildPath(
+      {android::base::Dirname(android::base::GetExecutablePath()),
+       "integration-tests", "CompileTest", "out.apk"});
+
+  const std::string source_set_res = BuildPath({"main", "res"});
+  const std::string relative_path_values_colors =
+      BuildPath({source_set_res, "values", "colors.xml"});
+  const std::string relative_path_layout_layout_one =
+      BuildPath({source_set_res, "layout", "layout_one.xml"});
+
+  CompileCommand(&diag).Execute({
+    path_values_colors,
+    "-o",
+    compiled_files_dir,
+    "--source-path",
+    relative_path_values_colors},
+        &std::cerr);
+
+  CompileCommand(&diag).Execute({
+    path_layout_layout_one,
+    "-o",
+    compiled_files_dir,
+    "--source-path",
+    relative_path_layout_layout_one},
+        &std::cerr);
+
+  std::ifstream ifs_values(path_values_colors_out);
+  std::string content_values((std::istreambuf_iterator<char>(ifs_values)),
+                             (std::istreambuf_iterator<char>()));
+  ASSERT_NE(content_values.find(relative_path_values_colors), -1);
+  ASSERT_EQ(content_values.find(path_values_colors), -1);
+
+  Link({"-o", apk_path, "--manifest", GetDefaultManifest(), "--proto-format"},
+      compiled_files_dir,  &diag);
+
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag);
+  ResourceTable* resource_table = apk.get()->GetResourceTable();
+  const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings =
+      resource_table->string_pool.strings();
+
+  ASSERT_EQ(pool_strings.size(), 2);
+  ASSERT_EQ(pool_strings[0]->value, "res/layout/layout_one.xml");
+  ASSERT_EQ(pool_strings[1]->value, "res/layout-v1/layout_one.xml");
+
+  // Check resources.pb contains relative sources.
+  io::IFile* proto_file =
+      apk.get()->GetFileCollection()->FindFile("resources.pb");
+  std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream();
+  io::ProtoInputStreamReader proto_reader(proto_stream.get());
+  pb::ResourceTable pb_table;
+  proto_reader.ReadMessage(&pb_table);
+
+  const std::string pool_strings_proto = pb_table.source_pool().data();
+
+  ASSERT_NE(pool_strings_proto.find(relative_path_values_colors), -1);
+  ASSERT_NE(pool_strings_proto.find(relative_path_layout_layout_one), -1);
+}
+
 }  // namespace aapt