Support for source_target_relative expansion in GN

This can be used to get a path to the source file relative to the target's
directory.

Review-Url: https://codereview.chromium.org/2387763002
Cr-Original-Commit-Position: refs/heads/master@{#439541}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 7d88638248b10cec98364cf561f268cbed62587e
diff --git a/tools/gn/action_values.cc b/tools/gn/action_values.cc
index a7ce83d..28c4a11 100644
--- a/tools/gn/action_values.cc
+++ b/tools/gn/action_values.cc
@@ -22,7 +22,7 @@
       target->output_type() == Target::ACTION_FOREACH) {
     // Copy and foreach applies the outputs to the sources.
     SubstitutionWriter::ApplyListToSources(
-        target->settings(), outputs_, target->sources(), result);
+        target, target->settings(), outputs_, target->sources(), result);
   } else {
     // Actions (and anything else that happens to specify an output) just use
     // the output list with no substitution.
diff --git a/tools/gn/bundle_file_rule.cc b/tools/gn/bundle_file_rule.cc
index e23361f..5c4fc27 100644
--- a/tools/gn/bundle_file_rule.cc
+++ b/tools/gn/bundle_file_rule.cc
@@ -46,7 +46,7 @@
         break;
       default:
         output_path.append(SubstitutionWriter::GetSourceSubstitution(
-            settings, source_file, subrange.type,
+            target_, target_->settings(), source_file, subrange.type,
             SubstitutionWriter::OUTPUT_ABSOLUTE, SourceDir()));
         break;
     }
diff --git a/tools/gn/desc_builder.cc b/tools/gn/desc_builder.cc
index 3e2aaa0..a1a5710 100644
--- a/tools/gn/desc_builder.cc
+++ b/tools/gn/desc_builder.cc
@@ -616,8 +616,9 @@
         res->SetWithoutPathExpansion("output_patterns", std::move(patterns));
       }
       std::vector<SourceFile> output_files;
-      SubstitutionWriter::ApplyListToSources(target_->settings(), outputs,
-                                             target_->sources(), &output_files);
+      SubstitutionWriter::ApplyListToSources(target_, target_->settings(),
+                                             outputs, target_->sources(),
+                                             &output_files);
       res->SetWithoutPathExpansion(variables::kOutputs,
                                    RenderValue(output_files));
     } else {
diff --git a/tools/gn/docs/reference.md b/tools/gn/docs/reference.md
index 1aa56f1..26ba5c6 100644
--- a/tools/gn/docs/reference.md
+++ b/tools/gn/docs/reference.md
@@ -6584,6 +6584,13 @@
       build.gn file.
         "//foo/bar/baz.txt" => "obj/foo/bar"
 
+  {{source_target_relative}}\n"
+      The path to the source file relative to the target's directory. This will
+      generally be used for replicating the source directory layout in the
+      output directory. This can only be used in actions and it is an error to
+      use in process_file_template where there is no "target".
+        "//foo/bar/baz.txt" => "baz.txt"
+
 ```
 
 ### **(*) Note on directories**
diff --git a/tools/gn/function_process_file_template.cc b/tools/gn/function_process_file_template.cc
index 78889c9..28e3e46 100644
--- a/tools/gn/function_process_file_template.cc
+++ b/tools/gn/function_process_file_template.cc
@@ -93,8 +93,15 @@
     return Value();
   }
 
+  auto& types = subst.required_types();
+  if (std::find(types.begin(), types.end(),
+                SUBSTITUTION_SOURCE_TARGET_RELATIVE) != types.end()) {
+    *err = Err(template_arg, "Not a valid substitution type for the function.");
+    return Value();
+  }
+
   SubstitutionWriter::ApplyListToSourcesAsString(
-      scope->settings(), subst, input_files, &result_files);
+      nullptr, scope->settings(), subst, input_files, &result_files);
 
   // Convert the list of strings to the return Value.
   Value ret(function, Value::LIST);
diff --git a/tools/gn/ninja_action_target_writer.cc b/tools/gn/ninja_action_target_writer.cc
index 1743632..77ee2b4 100644
--- a/tools/gn/ninja_action_target_writer.cc
+++ b/tools/gn/ninja_action_target_writer.cc
@@ -184,11 +184,11 @@
     // very unusual (normally the substitutions will go in one place or the
     // other) and the redundant assignment won't bother Ninja.
     SubstitutionWriter::WriteNinjaVariablesForSource(
-        settings_, sources[i],
+        target_, settings_, sources[i],
         target_->action_values().args().required_types(),
         args_escape_options, out_);
     SubstitutionWriter::WriteNinjaVariablesForSource(
-        settings_, sources[i],
+        target_, settings_, sources[i],
         target_->action_values().rsp_file_contents().required_types(),
         args_escape_options, out_);
 
@@ -206,7 +206,8 @@
   size_t first_output_index = output_files->size();
 
   SubstitutionWriter::ApplyListToSourceAsOutputFile(
-      settings_, target_->action_values().outputs(), source, output_files);
+      target_, settings_, target_->action_values().outputs(), source,
+      output_files);
 
   for (size_t i = first_output_index; i < output_files->size(); i++) {
     out_ << " ";
@@ -217,5 +218,5 @@
 void NinjaActionTargetWriter::WriteDepfile(const SourceFile& source) {
   path_output_.WriteFile(out_,
       SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
-          settings_, target_->action_values().depfile(), source));
+          target_, settings_, target_->action_values().depfile(), source));
 }
diff --git a/tools/gn/ninja_copy_target_writer.cc b/tools/gn/ninja_copy_target_writer.cc
index b0313be..9ffd0fc 100644
--- a/tools/gn/ninja_copy_target_writer.cc
+++ b/tools/gn/ninja_copy_target_writer.cc
@@ -103,7 +103,7 @@
   for (const auto& input_file : target_->sources()) {
     OutputFile output_file =
         SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
-            target_->settings(), output_subst, input_file);
+            target_, target_->settings(), output_subst, input_file);
     output_files->push_back(output_file);
 
     out_ << "build ";
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index 7730070..c6ea385 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -21,6 +21,7 @@
   "{{source_root_relative_dir}}",  // SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR
   "{{source_gen_dir}}",  // SUBSTITUTION_SOURCE_GEN_DIR
   "{{source_out_dir}}",  // SUBSTITUTION_SOURCE_OUT_DIR
+  "{{source_target_relative}}",  // SUBSTITUTION_SOURCE_TARGET_RELATIVE
 
   "{{label}}",  // SUBSTITUTION_LABEL
   "{{label_name}}",  // SUBSTITUTION_LABEL_NAME
@@ -70,6 +71,7 @@
     "source_root_relative_dir",  // SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR
     "source_gen_dir",            // SUBSTITUTION_SOURCE_GEN_DIR
     "source_out_dir",            // SUBSTITUTION_SOURCE_OUT_DIR
+    "source_target_relative",    // SUBSTITUTION_SOURCE_TARGET_RELATIVE
 
     "label",               // SUBSTITUTION_LABEL
     "label_name",          // SUBSTITUTION_LABEL_NAME
@@ -160,7 +162,8 @@
          type == SUBSTITUTION_SOURCE_DIR ||
          type == SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR ||
          type == SUBSTITUTION_SOURCE_GEN_DIR ||
-         type == SUBSTITUTION_SOURCE_OUT_DIR;
+         type == SUBSTITUTION_SOURCE_OUT_DIR ||
+         type == SUBSTITUTION_SOURCE_TARGET_RELATIVE;
 }
 
 bool IsValidToolSubstitution(SubstitutionType type) {
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index 84524fb..74ab507 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -30,6 +30,7 @@
   SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR,  // {{root_relative_dir}}
   SUBSTITUTION_SOURCE_GEN_DIR,  // {{source_gen_dir}}
   SUBSTITUTION_SOURCE_OUT_DIR,  // {{source_out_dir}}
+  SUBSTITUTION_SOURCE_TARGET_RELATIVE,  // {{source_target_relative}}
 
   // Valid for all compiler and linker tools. These depend on the target and
   // do not vary on a per-file basis.
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 4d818cd..7a3534e 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -98,6 +98,13 @@
       build.gn file.
         "//foo/bar/baz.txt" => "obj/foo/bar"
 
+  {{source_target_relative}}\n"
+      The path to the source file relative to the target's directory. This will
+      generally be used for replicating the source directory layout in the
+      output directory. This can only be used in actions and it is an error to
+      use in process_file_template where there is no "target".
+        "//foo/bar/baz.txt" => "baz.txt"
+
 (*) Note on directories
 
   Paths containing directories (except the source_root_relative_dir) will be
@@ -196,11 +203,12 @@
 
 // static
 SourceFile SubstitutionWriter::ApplyPatternToSource(
+      const Target* target,
       const Settings* settings,
       const SubstitutionPattern& pattern,
       const SourceFile& source) {
   std::string result_value = ApplyPatternToSourceAsString(
-      settings, pattern, source);
+      target, settings, pattern, source);
   CHECK(!result_value.empty() && result_value[0] == '/')
       << "The result of the pattern \""
       << pattern.AsString()
@@ -210,6 +218,7 @@
 
 // static
 std::string SubstitutionWriter::ApplyPatternToSourceAsString(
+    const Target* target,
     const Settings* settings,
     const SubstitutionPattern& pattern,
     const SourceFile& source) {
@@ -219,7 +228,7 @@
       result_value.append(subrange.literal);
     } else {
       result_value.append(
-          GetSourceSubstitution(settings, source, subrange.type,
+          GetSourceSubstitution(target, settings, source, subrange.type,
                                 OUTPUT_ABSOLUTE, SourceDir()));
     }
   }
@@ -228,78 +237,89 @@
 
 // static
 OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
+    const Target* target,
     const Settings* settings,
     const SubstitutionPattern& pattern,
     const SourceFile& source) {
-  SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source);
+  SourceFile result_as_source = ApplyPatternToSource(
+      target, settings, pattern, source);
   return OutputFile(settings->build_settings(), result_as_source);
 }
 
 // static
 void SubstitutionWriter::ApplyListToSource(
+    const Target* target,
     const Settings* settings,
     const SubstitutionList& list,
     const SourceFile& source,
     std::vector<SourceFile>* output) {
   for (const auto& item : list.list())
-    output->push_back(ApplyPatternToSource(settings, item, source));
+    output->push_back(ApplyPatternToSource(target, settings, item, source));
 }
 
 // static
 void SubstitutionWriter::ApplyListToSourceAsString(
+    const Target* target,
     const Settings* settings,
     const SubstitutionList& list,
     const SourceFile& source,
     std::vector<std::string>* output) {
   for (const auto& item : list.list())
-    output->push_back(ApplyPatternToSourceAsString(settings, item, source));
+    output->push_back(ApplyPatternToSourceAsString(
+        target, settings, item, source));
 }
 
 // static
 void SubstitutionWriter::ApplyListToSourceAsOutputFile(
+    const Target* target,
     const Settings* settings,
     const SubstitutionList& list,
     const SourceFile& source,
     std::vector<OutputFile>* output) {
   for (const auto& item : list.list())
-    output->push_back(ApplyPatternToSourceAsOutputFile(settings, item, source));
+    output->push_back(ApplyPatternToSourceAsOutputFile(
+        target, settings, item, source));
 }
 
 // static
 void SubstitutionWriter::ApplyListToSources(
+    const Target* target,
     const Settings* settings,
     const SubstitutionList& list,
     const std::vector<SourceFile>& sources,
     std::vector<SourceFile>* output) {
   output->clear();
   for (const auto& source : sources)
-    ApplyListToSource(settings, list, source, output);
+    ApplyListToSource(target, settings, list, source, output);
 }
 
 // static
 void SubstitutionWriter::ApplyListToSourcesAsString(
+    const Target* target,
     const Settings* settings,
     const SubstitutionList& list,
     const std::vector<SourceFile>& sources,
     std::vector<std::string>* output) {
   output->clear();
   for (const auto& source : sources)
-    ApplyListToSourceAsString(settings, list, source, output);
+    ApplyListToSourceAsString(target, settings, list, source, output);
 }
 
 // static
 void SubstitutionWriter::ApplyListToSourcesAsOutputFile(
+    const Target* target,
     const Settings* settings,
     const SubstitutionList& list,
     const std::vector<SourceFile>& sources,
     std::vector<OutputFile>* output) {
   output->clear();
   for (const auto& source : sources)
-    ApplyListToSourceAsOutputFile(settings, list, source, output);
+    ApplyListToSourceAsOutputFile(target, settings, list, source, output);
 }
 
 // static
 void SubstitutionWriter::WriteNinjaVariablesForSource(
+    const Target* target,
     const Settings* settings,
     const SourceFile& source,
     const std::vector<SubstitutionType>& types,
@@ -314,7 +334,8 @@
       out << "  " << kSubstitutionNinjaNames[type] << " = ";
         EscapeStringToStream(
             out,
-            GetSourceSubstitution(settings, source, type, OUTPUT_RELATIVE,
+            GetSourceSubstitution(target, settings, source, type,
+                                  OUTPUT_RELATIVE,
                                   settings->build_settings()->build_dir()),
             escape_options);
       out << std::endl;
@@ -324,6 +345,7 @@
 
 // static
 std::string SubstitutionWriter::GetSourceSubstitution(
+    const Target* target,
     const Settings* settings,
     const SourceFile& source,
     SubstitutionType type,
@@ -366,6 +388,16 @@
           BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
       break;
 
+    case SUBSTITUTION_SOURCE_TARGET_RELATIVE:
+      if (target) {
+        return RebasePath(source.value(), target->label().dir(),
+            settings->build_settings()->root_path_utf8());
+      }
+      NOTREACHED()
+          << "Cannot use substitution " << kSubstitutionNames[type]
+          << " without target";
+      return std::string();
+
     default:
       NOTREACHED()
           << "Unsupported substitution for this function: "
@@ -502,7 +534,7 @@
 
   // Fall-through to the source ones.
   return GetSourceSubstitution(
-      target->settings(), source, type, OUTPUT_RELATIVE,
+      target, target->settings(), source, type, OUTPUT_RELATIVE,
       target->settings()->build_settings()->build_dir());
 }
 
diff --git a/tools/gn/substitution_writer.h b/tools/gn/substitution_writer.h
index a450bbb..92f09c4 100644
--- a/tools/gn/substitution_writer.h
+++ b/tools/gn/substitution_writer.h
@@ -46,6 +46,10 @@
 // The compiler and linker specific substitutions do NOT include the various
 // cflags, ldflags, libraries, etc. These are written by the ninja target
 // writer since they depend on traversing the dependency tree.
+//
+// The methods which take a target as an argument can accept null target
+// pointer if there is no target context, in which case the substitutions
+// requiring target context will not work.
 class SubstitutionWriter {
  public:
   enum OutputStyle {
@@ -81,15 +85,20 @@
   // expected to be a SourceFile or an OutputFile, this will CHECK if the
   // result isn't in the correct directory. The caller should validate this
   // first (see for example IsFileInOuputDir).
+  //
+  // The target can be null (see class comment above).
   static SourceFile ApplyPatternToSource(
+      const Target* target,
       const Settings* settings,
       const SubstitutionPattern& pattern,
       const SourceFile& source);
   static std::string ApplyPatternToSourceAsString(
+      const Target* target,
       const Settings* settings,
       const SubstitutionPattern& pattern,
       const SourceFile& source);
   static OutputFile ApplyPatternToSourceAsOutputFile(
+      const Target* target,
       const Settings* settings,
       const SubstitutionPattern& pattern,
       const SourceFile& source);
@@ -98,17 +107,22 @@
   // given output vector. It works this way so one can call multiple times to
   // apply to multiple files and create a list. The result can either be
   // SourceFiles or OutputFiles.
+  //
+  // The target can be null (see class comment above).
   static void ApplyListToSource(
+      const Target* target,
       const Settings* settings,
       const SubstitutionList& list,
       const SourceFile& source,
       std::vector<SourceFile>* output);
   static void ApplyListToSourceAsString(
+      const Target* target,
       const Settings* settings,
       const SubstitutionList& list,
       const SourceFile& source,
       std::vector<std::string>* output);
   static void ApplyListToSourceAsOutputFile(
+      const Target* target,
       const Settings* settings,
       const SubstitutionList& list,
       const SourceFile& source,
@@ -116,17 +130,22 @@
 
   // Like ApplyListToSource but applies the list to all sources and replaces
   // rather than appends the output (this produces the complete output).
+  //
+  // The target can be null (see class comment above).
   static void ApplyListToSources(
+      const Target* target,
       const Settings* settings,
       const SubstitutionList& list,
       const std::vector<SourceFile>& sources,
       std::vector<SourceFile>* output);
   static void ApplyListToSourcesAsString(
+      const Target* target,
       const Settings* settings,
       const SubstitutionList& list,
       const std::vector<SourceFile>& sources,
       std::vector<std::string>* output);
   static void ApplyListToSourcesAsOutputFile(
+      const Target* target,
       const Settings* settings,
       const SubstitutionList& list,
       const std::vector<SourceFile>& sources,
@@ -138,7 +157,10 @@
   // Ninja files, paths will be relative to the build dir, and no definition
   // for {{source}} will be written since that maps to Ninja's implicit $in
   // variable.
+  //
+  // The target can be null (see class comment above).
   static void WriteNinjaVariablesForSource(
+      const Target* target,
       const Settings* settings,
       const SourceFile& source,
       const std::vector<SubstitutionType>& types,
@@ -149,7 +171,10 @@
   // given source file. If output_style is OUTPUT_RELATIVE, relative_to
   // indicates the directory that the relative directories should be relative
   // to, otherwise it is ignored.
+  //
+  // The target can be null (see class comment above).
   static std::string GetSourceSubstitution(
+      const Target* target,
       const Settings* settings,
       const SourceFile& source,
       SubstitutionType type,
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index d252c79..d98d4ce 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -43,7 +43,7 @@
                             nullptr, &err));
 
   SourceFile result = SubstitutionWriter::ApplyPatternToSource(
-      setup.settings(), pattern, SourceFile("//foo/bar/myfile.txt"));
+      nullptr, setup.settings(), pattern, SourceFile("//foo/bar/myfile.txt"));
   ASSERT_EQ("//out/Debug/gen/foo/bar/myfile.tmp", result.value());
 }
 
@@ -56,7 +56,7 @@
                             nullptr, &err));
 
   OutputFile result = SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
-      setup.settings(), pattern, SourceFile("//foo/bar/myfile.txt"));
+      nullptr, setup.settings(), pattern, SourceFile("//foo/bar/myfile.txt"));
   ASSERT_EQ("gen/foo/bar/myfile.tmp", result.value());
 }
 
@@ -73,7 +73,8 @@
 
   std::ostringstream out;
   SubstitutionWriter::WriteNinjaVariablesForSource(
-      setup.settings(), SourceFile("//foo/bar/baz.txt"), types, options, out);
+      nullptr, setup.settings(), SourceFile("//foo/bar/baz.txt"), types,
+      options, out);
 
   // The "source" should be skipped since that will expand to $in which is
   // implicit.
@@ -103,10 +104,17 @@
 
 TEST(SubstitutionWriter, SourceSubstitutions) {
   TestWithScope setup;
+  Err err;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/bar/"), "baz"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
 
   // Call to get substitutions relative to the build dir.
   #define GetRelSubst(str, what) \
       SubstitutionWriter::GetSourceSubstitution( \
+          &target, \
           setup.settings(), \
           SourceFile(str), \
           what, \
@@ -116,6 +124,7 @@
   // Call to get absolute directory substitutions.
   #define GetAbsSubst(str, what) \
       SubstitutionWriter::GetSourceSubstitution( \
+          &target, \
           setup.settings(), \
           SourceFile(str), \
           what, \
@@ -175,6 +184,11 @@
   EXPECT_EQ(".",
             GetRelSubst("//baz.txt", SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR));
 
+  EXPECT_EQ("baz.txt",
+      GetRelSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_TARGET_RELATIVE));
+  EXPECT_EQ("baz.txt",
+      GetAbsSubst("//foo/bar/baz.txt", SUBSTITUTION_SOURCE_TARGET_RELATIVE));
+
   #undef GetAbsSubst
   #undef GetRelSubst
 }