Support output_dir for Rust

This change implements support for output_dir expansion in Rust.

BUG=100

Change-Id: I79ffc677814f9f5d8e8eb7169c5ebb0ab4469c70
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/5740
Reviewed-by: Brett Wilson <brettw@google.com>
Commit-Queue: Petr Hosek <phosek@google.com>
diff --git a/tools/gn/c_substitution_type.cc b/tools/gn/c_substitution_type.cc
index 3054124..eec8c66 100644
--- a/tools/gn/c_substitution_type.cc
+++ b/tools/gn/c_substitution_type.cc
@@ -17,7 +17,7 @@
 
     &CSubstitutionLinkerInputs, &CSubstitutionLinkerInputsNewline,
     &CSubstitutionLdFlags,      &CSubstitutionLibs,
-    &CSubstitutionOutputDir,    &CSubstitutionOutputExtension,
+    &CSubstitutionOutputExtension,
     &CSubstitutionSoLibs,
 
     &CSubstitutionArFlags,
@@ -41,7 +41,6 @@
                                                       "in_newline"};
 const Substitution CSubstitutionLdFlags = {"{{ldflags}}", "ldflags"};
 const Substitution CSubstitutionLibs = {"{{libs}}", "libs"};
-const Substitution CSubstitutionOutputDir = {"{{output_dir}}", "output_dir"};
 const Substitution CSubstitutionOutputExtension = {"{{output_extension}}",
                                                   "output_extension"};
 const Substitution CSubstitutionSoLibs = {"{{solibs}}", "solibs"};
@@ -65,22 +64,22 @@
 }
 
 bool IsValidLinkerSubstitution(const Substitution* type) {
-  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
+  return IsValidToolSubstitution(type) || type == &SubstitutionOutputDir ||
+         type == &CSubstitutionLinkerInputs ||
          type == &CSubstitutionLinkerInputsNewline ||
          type == &CSubstitutionLdFlags || type == &CSubstitutionLibs ||
-         type == &CSubstitutionOutputDir ||
          type == &CSubstitutionOutputExtension || type == &CSubstitutionSoLibs;
 }
 
 bool IsValidLinkerOutputsSubstitution(const Substitution* type) {
   // All valid compiler outputs plus the output extension.
   return IsValidCompilerOutputsSubstitution(type) ||
-         type == &CSubstitutionOutputDir || type == &CSubstitutionOutputExtension;
+         type == &SubstitutionOutputDir || type == &CSubstitutionOutputExtension;
 }
 
 bool IsValidALinkSubstitution(const Substitution* type) {
-  return IsValidToolSubstitution(type) || type == &CSubstitutionLinkerInputs ||
+  return IsValidToolSubstitution(type) || type == &SubstitutionOutputDir ||
+         type == &CSubstitutionLinkerInputs ||
          type == &CSubstitutionLinkerInputsNewline ||
-         type == &CSubstitutionArFlags || type == &CSubstitutionOutputDir ||
-         type == &CSubstitutionOutputExtension;
+         type == &CSubstitutionArFlags || type == &CSubstitutionOutputExtension;
 }
diff --git a/tools/gn/c_substitution_type.h b/tools/gn/c_substitution_type.h
index eee5304..ed543f0 100644
--- a/tools/gn/c_substitution_type.h
+++ b/tools/gn/c_substitution_type.h
@@ -28,7 +28,6 @@
 extern const Substitution CSubstitutionLinkerInputsNewline;
 extern const Substitution CSubstitutionLdFlags;
 extern const Substitution CSubstitutionLibs;
-extern const Substitution CSubstitutionOutputDir;
 extern const Substitution CSubstitutionOutputExtension;
 extern const Substitution CSubstitutionSoLibs;
 
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
index f4590ad..04698b5 100644
--- a/tools/gn/ninja_c_binary_target_writer.cc
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -606,7 +606,7 @@
   out_ << std::endl;
   out_ << "  output_dir = "
        << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
-                                                    &CSubstitutionOutputDir);
+                                                    &SubstitutionOutputDir);
   out_ << std::endl;
 }
 
diff --git a/tools/gn/ninja_rust_binary_target_writer.cc b/tools/gn/ninja_rust_binary_target_writer.cc
index c39a207..142e542 100644
--- a/tools/gn/ninja_rust_binary_target_writer.cc
+++ b/tools/gn/ninja_rust_binary_target_writer.cc
@@ -81,6 +81,10 @@
   }
   WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts, out);
 
+  WriteVar(SubstitutionOutputDir.ninja_name,
+           SubstitutionWriter::GetLinkerSubstitution(target, tool,
+                                                     &SubstitutionOutputDir),
+           opts, out);
   if (!target->output_extension_set()) {
     DCHECK(tool->AsRust());
     WriteVar(kRustSubstitutionOutputExtension.ninja_name,
@@ -232,4 +236,4 @@
 void NinjaRustBinaryTargetWriter::WriteEdition() {
   DCHECK(!target_->rust_values().edition().empty());
   out_ << "  edition = " << target_->rust_values().edition() << std::endl;
-}
\ No newline at end of file
+}
diff --git a/tools/gn/ninja_rust_binary_target_writer_unittest.cc b/tools/gn/ninja_rust_binary_target_writer_unittest.cc
index 43bac89..9cac3e2 100644
--- a/tools/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -80,6 +80,7 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_dir = \n"
         "rustc_output_extension = \n"
         "rustflags =\n"
         "rustenv =\n"
@@ -121,6 +122,7 @@
     const char expected[] =
         "crate_name = mylib\n"
         "crate_type = rlib\n"
+        "output_dir = \n"
         "rustc_output_extension = .rlib\n"
         "rustc_output_prefix = lib\n"
         "rustflags =\n"
@@ -172,6 +174,7 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_dir = \n"
         "rustc_output_extension = \n"
         "rustflags =\n"
         "rustenv =\n"
@@ -229,6 +232,7 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_dir = \n"
         "rustc_output_extension = \n"
         "rustflags =\n"
         "rustenv =\n"
@@ -294,6 +298,7 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_dir = \n"
         "rustc_output_extension = \n"
         "rustflags =\n"
         "rustenv =\n"
@@ -309,4 +314,58 @@
     std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
-}
\ No newline at end of file
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustOutputExtensionAndDir) {
+  Err err;
+  TestWithScope setup;
+
+  Target source_set(setup.settings(), Label(SourceDir("//foo/"), "sources"));
+  source_set.set_output_type(Target::SOURCE_SET);
+  source_set.visibility().SetPublic();
+  source_set.sources().push_back(SourceFile("//foo/input1.rs"));
+  source_set.sources().push_back(SourceFile("//foo/input2.rs"));
+  source_set.source_types_used().Set(SourceFile::SOURCE_RS);
+  source_set.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(source_set.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(SourceFile("//foo/input3.rs"));
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.set_output_extension(std::string("exe"));
+  target.set_output_dir(SourceDir("//out/Debug/foo/"));
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.rust_values().edition() = "2018";
+  target.private_deps().push_back(LabelTargetPair(&source_set));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_dir = foo\n"
+        "rustc_output_extension = .exe\n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/foo_bar.exe: rustc ../../foo/main.rs | ../../foo/input3.rs "
+        "../../foo/main.rs ../../foo/input1.rs ../../foo/input2.rs || "
+        "obj/foo/sources.stamp\n"
+        "  edition = 2018\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << out_str;
+  }
+}
diff --git a/tools/gn/rust_substitution_type.cc b/tools/gn/rust_substitution_type.cc
index 83ddbf5..c6b8e9e 100644
--- a/tools/gn/rust_substitution_type.cc
+++ b/tools/gn/rust_substitution_type.cc
@@ -35,6 +35,7 @@
 
 bool IsValidRustSubstitution(const Substitution* type) {
   return IsValidToolSubstitution(type) || IsValidSourceSubstitution(type) ||
+         type == &SubstitutionOutputDir ||
          type == &kRustSubstitutionCrateName ||
          type == &kRustSubstitutionCrateType ||
          type == &kRustSubstitutionEdition ||
@@ -44,4 +45,4 @@
          type == &kRustSubstitutionRustDeps ||
          type == &kRustSubstitutionRustEnv ||
          type == &kRustSubstitutionRustFlags;
-}
\ No newline at end of file
+}
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index 6972beb..2f6e23e 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -22,6 +22,7 @@
     &SubstitutionLabelName,
     &SubstitutionRootGenDir,
     &SubstitutionRootOutDir,
+    &SubstitutionOutputDir,
     &SubstitutionTargetGenDir,
     &SubstitutionTargetOutDir,
     &SubstitutionTargetOutputName,
@@ -73,6 +74,7 @@
                                              "root_gen_dir"};
 const Substitution SubstitutionRootOutDir = {"{{root_out_dir}}",
                                              "root_out_dir"};
+const Substitution SubstitutionOutputDir = {"{{output_dir}}", "output_dir"};
 const Substitution SubstitutionTargetGenDir = {"{{target_gen_dir}}",
                                                "target_gen_dir"};
 const Substitution SubstitutionTargetOutDir = {"{{target_out_dir}}",
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index 5bdbf90..45e890c 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -39,6 +39,7 @@
 extern const Substitution SubstitutionLabelName;
 extern const Substitution SubstitutionRootGenDir;
 extern const Substitution SubstitutionRootOutDir;
+extern const Substitution SubstitutionOutputDir;
 extern const Substitution SubstitutionTargetGenDir;
 extern const Substitution SubstitutionTargetOutDir;
 extern const Substitution SubstitutionTargetOutputName;
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 8ce1105..41be9b4 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -547,7 +547,7 @@
     return result;
 
   // Fall-through to the linker-specific ones.
-  if (type == &CSubstitutionOutputDir) {
+  if (type == &SubstitutionOutputDir) {
     // Use the target's value if there is one (it will have no expansion
     // patterns since it can directly use GN variables to compute whatever
     // path it wants), or the tool's default (which will contain further