Provide separate Rust tools for different output types

This introduces a set of new tools corresponding to different Rust
output types/crate types: cdylib, dylib, proc_macro, rlib and staticlib.

This enables using different setting for different output types, e.g.
Rust and static libraris should typically go into different output dirs
akin to their C counterparts. On the other hand, executables and dynamic
libraries might need additional post-processing such as stripping. This
wasn't possible with a single tool because there's no way conditionalize
the logic used by the tool.

Change-Id: I2dc2ad927a2a0f55b995abbf269bb7fcd0037c49
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6220
Commit-Queue: Petr Hosek <phosek@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index c9dba3d..c4dda86 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -2041,8 +2041,9 @@
       the current scope is not (since the overrides haven't been applied yet).
 
    2. At the end of executing the block, any variables set within that scope
-      are saved globally as build arguments, with their current values being
-      saved as the "default value" for that argument.
+      are saved, with the values specified in the block used as the "default value"
+      for that argument. Once saved, these variables are available for override
+      via args.gn.
 
    3. User-defined overrides are applied. Anything set in "gn args" now
       overrides any default values. The resulting set of variables is promoted
@@ -3175,7 +3176,12 @@
       "compile_xcassets": [iOS, macOS] Tool to compile asset catalogs.
 
     Rust tools:
-      "rustc": Rust compiler and linker
+      "rust_bin": Tool for compiling Rust binaries
+      "rust_cdylib": Tool for compiling C-compatible dynamic libraries.
+      "rust_dylib": Tool for compiling Rust dynamic libraries.
+      "rust_macro": Tool for compiling Rust procedural macros.
+      "rust_rlib": Tool for compiling Rust libraries.
+      "rust_staticlib": Tool for compiling Rust static libraries.
 ```
 
 #### **Tool variables**
@@ -3592,13 +3598,6 @@
         Expands to the list of --extern flags needed to include addition Rust
         libraries in this target. Includes any specified renamed dependencies.
 
-    {{rustc_output_extension}}
-        Expands to the output extension for this target's crate type.
-
-    {{rustc_output_prefix}}
-        Expands to the prefix for shared and static libraries. This should
-        generally be "lib". Empty for executable targets.
-
     {{rustdeps}}
         Expands to the list of -Ldependency=<path> strings needed to compile
         this target.
diff --git a/tools/gn/c_substitution_type.cc b/tools/gn/c_substitution_type.cc
index eec8c66..98cd139 100644
--- a/tools/gn/c_substitution_type.cc
+++ b/tools/gn/c_substitution_type.cc
@@ -17,7 +17,6 @@
 
     &CSubstitutionLinkerInputs, &CSubstitutionLinkerInputsNewline,
     &CSubstitutionLdFlags,      &CSubstitutionLibs,
-    &CSubstitutionOutputExtension,
     &CSubstitutionSoLibs,
 
     &CSubstitutionArFlags,
@@ -41,8 +40,6 @@
                                                       "in_newline"};
 const Substitution CSubstitutionLdFlags = {"{{ldflags}}", "ldflags"};
 const Substitution CSubstitutionLibs = {"{{libs}}", "libs"};
-const Substitution CSubstitutionOutputExtension = {"{{output_extension}}",
-                                                  "output_extension"};
 const Substitution CSubstitutionSoLibs = {"{{solibs}}", "solibs"};
 
 // Valid for alink only.
@@ -64,22 +61,27 @@
 }
 
 bool IsValidLinkerSubstitution(const Substitution* type) {
-  return IsValidToolSubstitution(type) || type == &SubstitutionOutputDir ||
+  return IsValidToolSubstitution(type) ||
+         type == &SubstitutionOutputDir ||
+         type == &SubstitutionOutputExtension ||
          type == &CSubstitutionLinkerInputs ||
          type == &CSubstitutionLinkerInputsNewline ||
-         type == &CSubstitutionLdFlags || type == &CSubstitutionLibs ||
-         type == &CSubstitutionOutputExtension || type == &CSubstitutionSoLibs;
+         type == &CSubstitutionLdFlags ||
+         type == &CSubstitutionLibs ||
+         type == &CSubstitutionSoLibs;
 }
 
 bool IsValidLinkerOutputsSubstitution(const Substitution* type) {
   // All valid compiler outputs plus the output extension.
   return IsValidCompilerOutputsSubstitution(type) ||
-         type == &SubstitutionOutputDir || type == &CSubstitutionOutputExtension;
+         type == &SubstitutionOutputDir || type == &SubstitutionOutputExtension;
 }
 
 bool IsValidALinkSubstitution(const Substitution* type) {
-  return IsValidToolSubstitution(type) || type == &SubstitutionOutputDir ||
+  return IsValidToolSubstitution(type) ||
+         type == &SubstitutionOutputDir ||
+         type == &SubstitutionOutputExtension ||
          type == &CSubstitutionLinkerInputs ||
          type == &CSubstitutionLinkerInputsNewline ||
-         type == &CSubstitutionArFlags || type == &CSubstitutionOutputExtension;
+         type == &CSubstitutionArFlags;
 }
diff --git a/tools/gn/c_substitution_type.h b/tools/gn/c_substitution_type.h
index ed543f0..f4801ea 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 CSubstitutionOutputExtension;
 extern const Substitution CSubstitutionSoLibs;
 
 // Valid for alink only.
diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc
index cb68612..c8e85de 100644
--- a/tools/gn/function_toolchain.cc
+++ b/tools/gn/function_toolchain.cc
@@ -300,7 +300,12 @@
       "compile_xcassets": [iOS, macOS] Tool to compile asset catalogs.
 
     Rust tools:
-      "rustc": Rust compiler and linker
+      "rust_bin": Tool for compiling Rust binaries
+      "rust_cdylib": Tool for compiling C-compatible dynamic libraries.
+      "rust_dylib": Tool for compiling Rust dynamic libraries.
+      "rust_macro": Tool for compiling Rust procedural macros.
+      "rust_rlib": Tool for compiling Rust libraries.
+      "rust_staticlib": Tool for compiling Rust static libraries.
 
 Tool variables
 
@@ -715,13 +720,6 @@
         Expands to the list of --extern flags needed to include addition Rust
         libraries in this target. Includes any specified renamed dependencies.
 
-    {{rustc_output_extension}}
-        Expands to the output extension for this target's crate type.
-
-    {{rustc_output_prefix}}
-        Expands to the prefix for shared and static libraries. This should
-        generally be "lib". Empty for executable targets.
-
     {{rustdeps}}
         Expands to the list of -Ldependency=<path> strings needed to compile
         this target.
diff --git a/tools/gn/function_toolchain_unittest.cc b/tools/gn/function_toolchain_unittest.cc
index baf2e26..a10f729 100644
--- a/tools/gn/function_toolchain_unittest.cc
+++ b/tools/gn/function_toolchain_unittest.cc
@@ -67,7 +67,7 @@
   {
     TestParseInput input(
         R"(toolchain("rust") {
-          tool("rustc") {
+          tool("rust_bin") {
             command = "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin {{rustflags}} -o {{output}} {{externs}} {{source}}"
             description = "RUST {{output}}"
           }
@@ -83,7 +83,7 @@
     const Toolchain* toolchain = setup.items()[0]->AsToolchain();
     ASSERT_TRUE(toolchain);
 
-    const Tool* rust = toolchain->GetTool(RustTool::kRsToolRustc);
+    const Tool* rust = toolchain->GetTool(RustTool::kRsToolBin);
     ASSERT_TRUE(rust);
     ASSERT_EQ(rust->command().AsString(),
               "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin "
@@ -116,4 +116,4 @@
   const Tool* link = toolchain->GetTool(CTool::kCToolCxx);
   ASSERT_TRUE(link);
   EXPECT_EQ("/usr/goma/gomacc", link->command_launcher());
-}
\ No newline at end of file
+}
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
index a777cbf..f71eebb 100644
--- a/tools/gn/ninja_c_binary_target_writer.cc
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -548,7 +548,7 @@
 void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() {
   out_ << "  output_extension = "
        << SubstitutionWriter::GetLinkerSubstitution(
-              target_, tool_, &CSubstitutionOutputExtension);
+              target_, tool_, &SubstitutionOutputExtension);
   out_ << std::endl;
   out_ << "  output_dir = "
        << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
diff --git a/tools/gn/ninja_c_binary_target_writer_unittest.cc b/tools/gn/ninja_c_binary_target_writer_unittest.cc
index 874616f..74a4f32 100644
--- a/tools/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_c_binary_target_writer_unittest.cc
@@ -1227,7 +1227,7 @@
         "\n"
         "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n"
         "\n"
-        "build ./bar: link obj/bar/bar.bar.o obj/foo/foo.a\n"
+        "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
         "  ldflags =\n"
         "  libs =\n"
         "  output_extension = \n"
@@ -1285,7 +1285,7 @@
         "\n"
         "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n"
         "\n"
-        "build ./bar: link obj/bar/bar.bar.o obj/foo/foo.a\n"
+        "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
         "  ldflags =\n"
         "  libs =\n"
         "  output_extension = \n"
@@ -1344,7 +1344,7 @@
         "\n"
         "build obj/bar/bar.bar.o: cxx ../../bar/bar.cc\n"
         "\n"
-        "build ./bar: link obj/bar/bar.bar.o obj/foo/foo.a\n"
+        "build ./bar: link obj/bar/bar.bar.o obj/foo/libfoo.a\n"
         "  ldflags =\n"
         "  libs =\n"
         "  output_extension = \n"
diff --git a/tools/gn/ninja_rust_binary_target_writer.cc b/tools/gn/ninja_rust_binary_target_writer.cc
index cd81b43..51e585d 100644
--- a/tools/gn/ninja_rust_binary_target_writer.cc
+++ b/tools/gn/ninja_rust_binary_target_writer.cc
@@ -83,26 +83,14 @@
   }
   WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts, out);
 
+  WriteVar(SubstitutionOutputExtension.ninja_name,
+           SubstitutionWriter::GetLinkerSubstitution(target, tool,
+                                                     &SubstitutionOutputExtension),
+           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,
-             tool->AsRust()->rustc_output_extension(
-                 target->output_type(), target->rust_values().crate_type()),
-             opts, out);
-  } else if (target->output_extension().empty()) {
-    WriteVar(kRustSubstitutionOutputExtension.ninja_name, "", opts, out);
-  } else {
-    WriteVar(kRustSubstitutionOutputExtension.ninja_name,
-             std::string(".") + target->output_extension(), opts, out);
-  }
-
-  if (target->output_type() == Target::RUST_LIBRARY ||
-      target->output_type() == Target::SHARED_LIBRARY)
-    WriteVar(kRustSubstitutionOutputPrefix.ninja_name, "lib", opts, out);
 }
 
 }  // namespace
@@ -183,11 +171,11 @@
   WriteCrateVars(target_, tool_, opts, out_);
 
   WriteOneFlag(target_, &kRustSubstitutionRustFlags, false,
-               RustTool::kRsToolRustc, &ConfigValues::rustflags, opts,
+               Tool::kToolNone, &ConfigValues::rustflags, opts,
                path_output_, out_);
 
   WriteOneFlag(target_, &kRustSubstitutionRustEnv, false,
-               RustTool::kRsToolRustc, &ConfigValues::rustenv, opts,
+               Tool::kToolNone, &ConfigValues::rustenv, opts,
                path_output_, out_);
 
   WriteSharedVars(subst);
diff --git a/tools/gn/ninja_rust_binary_target_writer_unittest.cc b/tools/gn/ninja_rust_binary_target_writer_unittest.cc
index ed2e547..4eb7eea 100644
--- a/tools/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -80,15 +80,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/input3.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input3.rs "
         "../../foo/main.rs ../../foo/input1.rs ../../foo/input2.rs || "
         "obj/foo/sources.stamp\n"
         "  rustdeps =\n";
@@ -121,20 +121,19 @@
     const char expected[] =
         "crate_name = mylib\n"
         "crate_type = rlib\n"
+        "output_extension = .rlib\n"
         "output_dir = \n"
-        "rustc_output_extension = .rlib\n"
-        "rustc_output_prefix = lib\n"
         "rustflags =\n"
         "rustenv =\n"
         "root_out_dir = .\n"
         "target_out_dir = obj/bar\n"
-        "target_output_name = mylib\n"
+        "target_output_name = libmylib\n"
         "\n"
-        "build obj/bar/libmylib.rlib: rustc ../../bar/lib.rs | "
+        "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | "
         "../../bar/mylib.rs ../../bar/lib.rs\n"
         "  rustdeps =\n";
     std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str) << out_str;
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
 
   Target another_rlib(setup.settings(), Label(SourceDir("//foo/"), "direct"));
@@ -171,15 +170,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs obj/foo/libdirect.rlib obj/bar/libmylib.rlib\n"
         "  externs = --extern direct=obj/foo/libdirect.rlib --extern "
         "mylib=obj/bar/libmylib.rlib\n"
@@ -227,15 +226,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs obj/foo/libdirect.rlib\n"
         "  externs = --extern direct_renamed=obj/foo/libdirect.rlib\n"
         "  rustdeps = -Ldependency=obj/foo\n";
@@ -290,15 +289,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs obj/bar/libmylib.rlib obj/foo/libstatic.a\n"
         "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
         "  rustdeps = -Ldependency=obj/bar -Lnative=obj/foo -lstatic\n";
@@ -326,15 +325,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs obj/foo/libstatic.a\n"
         "  rustdeps = -Lnative=obj/foo -lstatic\n";
     std::string out_str = out.str();
@@ -378,15 +377,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = .exe\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 "
+        "build ./foo_bar.exe: rust_bin ../../foo/main.rs | ../../foo/input3.rs "
         "../../foo/main.rs ../../foo/input1.rs ../../foo/input2.rs || "
         "obj/foo/sources.stamp\n"
         "  rustdeps =\n";
@@ -422,15 +421,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = foo\n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/input.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input.rs "
         "../../foo/main.rs\n"
         "  rustdeps = -Lnative=../../baz -lquux\n";
     std::string out_str = out.str();
@@ -463,19 +462,19 @@
     const char expected[] =
         "crate_name = mymacro\n"
         "crate_type = proc-macro\n"
+        "output_extension = .so\n"
         "output_dir = \n"
-        "rustc_output_extension = .so\n"
         "rustflags =\n"
         "rustenv =\n"
         "root_out_dir = .\n"
         "target_out_dir = obj/bar\n"
-        "target_output_name = mymacro\n"
+        "target_output_name = libmymacro\n"
         "\n"
-        "build obj/bar/libmymacro.so: rustc ../../bar/lib.rs | "
+        "build obj/bar/libmymacro.so: rust_macro ../../bar/lib.rs | "
         "../../bar/mylib.rs ../../bar/lib.rs\n"
         "  rustdeps =\n";
     std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str) << out_str;
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
 
   Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
@@ -499,15 +498,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs || obj/bar/libmymacro.so\n"
         "  externs = --extern mymacro=obj/bar/libmymacro.so\n"
         "  rustdeps = -Ldependency=obj/bar\n";
@@ -540,20 +539,19 @@
     const char expected[] =
         "crate_name = mylib\n"
         "crate_type = rlib\n"
+        "output_extension = .rlib\n"
         "output_dir = \n"
-        "rustc_output_extension = .rlib\n"
-        "rustc_output_prefix = lib\n"
         "rustflags =\n"
         "rustenv =\n"
         "root_out_dir = .\n"
         "target_out_dir = obj/bar\n"
-        "target_output_name = mylib\n"
+        "target_output_name = libmylib\n"
         "\n"
-        "build obj/bar/libmylib.rlib: rustc ../../bar/lib.rs | "
+        "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | "
         "../../bar/mylib.rs ../../bar/lib.rs\n"
         "  rustdeps =\n";
     std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str) << out_str;
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
 
   Target group(setup.settings(), Label(SourceDir("//baz/"), "group"));
@@ -584,15 +582,15 @@
     const char expected[] =
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
+        "output_extension = \n"
         "output_dir = \n"
-        "rustc_output_extension = \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: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs obj/bar/libmylib.rlib || obj/baz/group.stamp\n"
         "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
         "  rustdeps = -Ldependency=obj/bar\n";
diff --git a/tools/gn/rust_substitution_type.cc b/tools/gn/rust_substitution_type.cc
index 8881772..6ce29f0 100644
--- a/tools/gn/rust_substitution_type.cc
+++ b/tools/gn/rust_substitution_type.cc
@@ -12,7 +12,6 @@
 
 const SubstitutionTypes RustSubstitutions = {
     &kRustSubstitutionCrateName,       &kRustSubstitutionCrateType,
-    &kRustSubstitutionOutputExtension, &kRustSubstitutionOutputPrefix,
     &kRustSubstitutionRustDeps,        &kRustSubstitutionRustFlags,
     &kRustSubstitutionRustEnv,         &kRustSubstitutionExterns,
 };
@@ -23,10 +22,6 @@
 const Substitution kRustSubstitutionCrateType = {"{{crate_type}}",
                                                  "crate_type"};
 const Substitution kRustSubstitutionExterns = {"{{externs}}", "externs"};
-const Substitution kRustSubstitutionOutputExtension = {
-    "{{rustc_output_extension}}", "rustc_output_extension"};
-const Substitution kRustSubstitutionOutputPrefix = {"{{rustc_output_prefix}}",
-                                                    "rustc_output_prefix"};
 const Substitution kRustSubstitutionRustDeps = {"{{rustdeps}}", "rustdeps"};
 const Substitution kRustSubstitutionRustEnv = {"{{rustenv}}", "rustenv"};
 const Substitution kRustSubstitutionRustFlags = {"{{rustflags}}", "rustflags"};
@@ -34,11 +29,10 @@
 bool IsValidRustSubstitution(const Substitution* type) {
   return IsValidToolSubstitution(type) || IsValidSourceSubstitution(type) ||
          type == &SubstitutionOutputDir ||
+         type == &SubstitutionOutputExtension ||
          type == &kRustSubstitutionCrateName ||
          type == &kRustSubstitutionCrateType ||
          type == &kRustSubstitutionExterns ||
-         type == &kRustSubstitutionOutputExtension ||
-         type == &kRustSubstitutionOutputPrefix ||
          type == &kRustSubstitutionRustDeps ||
          type == &kRustSubstitutionRustEnv ||
          type == &kRustSubstitutionRustFlags;
diff --git a/tools/gn/rust_substitution_type.h b/tools/gn/rust_substitution_type.h
index e9a78c4..85fc20c 100644
--- a/tools/gn/rust_substitution_type.h
+++ b/tools/gn/rust_substitution_type.h
@@ -17,8 +17,6 @@
 extern const Substitution kRustSubstitutionCrateName;
 extern const Substitution kRustSubstitutionCrateType;
 extern const Substitution kRustSubstitutionExterns;
-extern const Substitution kRustSubstitutionOutputExtension;
-extern const Substitution kRustSubstitutionOutputPrefix;
 extern const Substitution kRustSubstitutionRustDeps;
 extern const Substitution kRustSubstitutionRustEnv;
 extern const Substitution kRustSubstitutionRustFlags;
diff --git a/tools/gn/rust_tool.cc b/tools/gn/rust_tool.cc
index 563b20b..adf3c94 100644
--- a/tools/gn/rust_tool.cc
+++ b/tools/gn/rust_tool.cc
@@ -7,9 +7,14 @@
 #include "tools/gn/rust_substitution_type.h"
 #include "tools/gn/target.h"
 
-const char* RustTool::kRsToolRustc = "rustc";
+const char* RustTool::kRsToolBin = "rust_bin";
+const char* RustTool::kRsToolCDylib = "rust_cdylib";
+const char* RustTool::kRsToolDylib = "rust_dylib";
+const char* RustTool::kRsToolMacro = "rust_macro";
+const char* RustTool::kRsToolRlib = "rust_rlib";
+const char* RustTool::kRsToolStaticlib = "rust_staticlib";
 
-RustTool::RustTool(const char* n) : Tool(n), rlib_output_extension_(".rlib") {
+RustTool::RustTool(const char* n) : Tool(n) {
   CHECK(ValidateName(n));
   // TODO: should these be settable in toolchain definition?
   set_framework_switch("-lframework=");
@@ -28,7 +33,9 @@
 }
 
 bool RustTool::ValidateName(const char* name) const {
-  return name_ == kRsToolRustc;
+  return name == kRsToolBin || name == kRsToolCDylib ||
+         name == kRsToolDylib || name == kRsToolMacro ||
+         name == kRsToolRlib || name == kRsToolStaticlib;
 }
 
 void RustTool::SetComplete() {
@@ -50,28 +57,6 @@
   return true;
 }
 
-bool RustTool::ReadOutputExtensions(Scope* scope, Err* err) {
-  if (!SetOutputExtension(scope->GetValue("exe_output_extension", true),
-                          &exe_output_extension_, err))
-    return false;
-  if (!SetOutputExtension(scope->GetValue("rlib_output_extension", true),
-                          &rlib_output_extension_, err))
-    return false;
-  if (!SetOutputExtension(scope->GetValue("dylib_output_extension", true),
-                          &dylib_output_extension_, err))
-    return false;
-  if (!SetOutputExtension(scope->GetValue("cdylib_output_extension", true),
-                          &cdylib_output_extension_, err))
-    return false;
-  if (!SetOutputExtension(scope->GetValue("staticlib_output_extension", true),
-                          &staticlib_output_extension_, err))
-    return false;
-  if (!SetOutputExtension(scope->GetValue("proc_macro_output_extension", true),
-                          &proc_macro_output_extension_, err))
-    return false;
-  return true;
-}
-
 bool RustTool::ReadOutputsPatternList(Scope* scope,
                                       const char* var,
                                       SubstitutionList* field,
@@ -112,10 +97,6 @@
     return false;
   }
 
-  if (!ReadOutputExtensions(scope, err)) {
-    return false;
-  }
-
   // All Rust tools should have outputs.
   if (!ReadOutputsPatternList(scope, "outputs", &outputs_, err)) {
     return false;
@@ -124,37 +105,10 @@
 }
 
 bool RustTool::ValidateSubstitution(const Substitution* sub_type) const {
-  if (name_ == kRsToolRustc)
+  if (name_ == kRsToolBin || name_ == kRsToolCDylib ||
+      name_ == kRsToolDylib || name_ == kRsToolMacro ||
+      name_ == kRsToolRlib || name_ == kRsToolStaticlib)
     return IsValidRustSubstitution(sub_type);
   NOTREACHED();
   return false;
 }
-
-const std::string& RustTool::rustc_output_extension(
-    Target::OutputType type,
-    const RustValues::CrateType crate_type) const {
-  switch (crate_type) {
-    case RustValues::CRATE_AUTO: {
-      switch (type) {
-        case Target::EXECUTABLE:
-          return exe_output_extension_;
-        case Target::STATIC_LIBRARY:
-          return staticlib_output_extension_;
-        case Target::RUST_LIBRARY:
-          return rlib_output_extension_;
-        default:
-          NOTREACHED();
-          return exe_output_extension_;
-      }
-    }
-    case RustValues::CRATE_DYLIB:
-      return dylib_output_extension_;
-    case RustValues::CRATE_CDYLIB:
-      return cdylib_output_extension_;
-    case RustValues::CRATE_PROC_MACRO:
-      return proc_macro_output_extension_;
-    default:
-      NOTREACHED();
-      return exe_output_extension_;
-  }
-}
diff --git a/tools/gn/rust_tool.h b/tools/gn/rust_tool.h
index 682f74a..46ec801 100644
--- a/tools/gn/rust_tool.h
+++ b/tools/gn/rust_tool.h
@@ -21,7 +21,12 @@
 class RustTool : public Tool {
  public:
   // Rust tools
-  static const char* kRsToolRustc;
+  static const char* kRsToolBin;
+  static const char* kRsToolCDylib;
+  static const char* kRsToolDylib;
+  static const char* kRsToolMacro;
+  static const char* kRsToolRlib;
+  static const char* kRsToolStaticlib;
 
   explicit RustTool(const char* n);
   ~RustTool();
@@ -36,62 +41,13 @@
   RustTool* AsRust() override;
   const RustTool* AsRust() const override;
 
-  void set_exe_output_extension(std::string ext) {
-    DCHECK(!complete_);
-    DCHECK(ext.empty() || ext[0] == '.');
-    exe_output_extension_ = std::move(ext);
-  }
-
-  void set_rlib_output_extension(std::string ext) {
-    DCHECK(!complete_);
-    DCHECK(ext.empty() || ext[0] == '.');
-    rlib_output_extension_ = std::move(ext);
-  }
-
-  void set_dylib_output_extension(std::string ext) {
-    DCHECK(!complete_);
-    DCHECK(ext.empty() || ext[0] == '.');
-    dylib_output_extension_ = std::move(ext);
-  }
-
-  void set_cdylib_output_extension(std::string ext) {
-    DCHECK(!complete_);
-    DCHECK(ext.empty() || ext[0] == '.');
-    cdylib_output_extension_ = std::move(ext);
-  }
-
-  void set_staticlib_output_extension(std::string ext) {
-    DCHECK(!complete_);
-    DCHECK(ext.empty() || ext[0] == '.');
-    staticlib_output_extension_ = std::move(ext);
-  }
-
-  void set_proc_macro_output_extension(std::string ext) {
-    DCHECK(!complete_);
-    DCHECK(ext.empty() || ext[0] == '.');
-    proc_macro_output_extension_ = std::move(ext);
-  }
-
-  // Will include a leading "." if nonempty.
-  const std::string& rustc_output_extension(
-      Target::OutputType type,
-      const RustValues::CrateType crate_type) const;
-
  private:
   bool SetOutputExtension(const Value* value, std::string* var, Err* err);
-  bool ReadOutputExtensions(Scope* scope, Err* err);
   bool ReadOutputsPatternList(Scope* scope,
                               const char* var,
                               SubstitutionList* field,
                               Err* err);
 
-  std::string exe_output_extension_;
-  std::string rlib_output_extension_;
-  std::string dylib_output_extension_;
-  std::string cdylib_output_extension_;
-  std::string staticlib_output_extension_;
-  std::string proc_macro_output_extension_;
-
   DISALLOW_COPY_AND_ASSIGN(RustTool);
 };
 
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index 2f6e23e..4a5f338 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -23,6 +23,7 @@
     &SubstitutionRootGenDir,
     &SubstitutionRootOutDir,
     &SubstitutionOutputDir,
+    &SubstitutionOutputExtension,
     &SubstitutionTargetGenDir,
     &SubstitutionTargetOutDir,
     &SubstitutionTargetOutputName,
@@ -75,6 +76,8 @@
 const Substitution SubstitutionRootOutDir = {"{{root_out_dir}}",
                                              "root_out_dir"};
 const Substitution SubstitutionOutputDir = {"{{output_dir}}", "output_dir"};
+const Substitution SubstitutionOutputExtension = {"{{output_extension}}",
+                                                  "output_extension"};
 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 45e890c..5be6e0d 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -40,6 +40,7 @@
 extern const Substitution SubstitutionRootGenDir;
 extern const Substitution SubstitutionRootOutDir;
 extern const Substitution SubstitutionOutputDir;
+extern const Substitution SubstitutionOutputExtension;
 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 8b884c6..99eaaf8 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -562,7 +562,7 @@
                    target->settings()->build_settings()->build_dir()),
         &result);
     return result;
-  } else if (type == &CSubstitutionOutputExtension) {
+  } else if (type == &SubstitutionOutputExtension) {
     // Use the extension provided on the target if specified, otherwise
     // fall back on the default. Note that the target's output extension
     // does not include the dot but the tool's does.
@@ -574,23 +574,6 @@
   } else if (type == &kRustSubstitutionCrateName) {
     // Only include the toolchain for non-default toolchains.
     return target->rust_values().crate_name();
-  } else if (type == &kRustSubstitutionOutputPrefix) {
-    // Rustc expects specific output prefixes, so make sure we provide it if
-    // necessary.
-    if (target->output_type() == Target::RUST_LIBRARY ||
-        target->output_type() == Target::SHARED_LIBRARY ||
-        target->output_type() == Target::LOADABLE_MODULE)
-      return "lib";
-    return "";
-  } else if (type == &kRustSubstitutionOutputExtension) {
-    if (!target->output_extension_set()) {
-      DCHECK(tool->AsRust());
-      return tool->AsRust()->rustc_output_extension(
-          target->output_type(), target->rust_values().crate_type());
-    }
-    if (target->output_extension().empty())
-      return std::string();  // Explicitly set to no extension.
-    return std::string(".") + target->output_extension();
   } else {
     NOTREACHED();
     return std::string();
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index bea8fec..0dea906 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -253,7 +253,7 @@
   // The compiler substitution is just target + OUTPUT_EXTENSION combined. So
   // test one target one plus the output extension.
   EXPECT_EQ(".so", SubstitutionWriter::GetLinkerSubstitution(
-                       &target, tool, &CSubstitutionOutputExtension));
+                       &target, tool, &SubstitutionOutputExtension));
   EXPECT_EQ("gen/foo/bar", SubstitutionWriter::GetLinkerSubstitution(
                                &target, tool, &SubstitutionTargetGenDir));
 
@@ -270,10 +270,10 @@
   // Output extensions can be overridden.
   target.set_output_extension("extension");
   EXPECT_EQ(".extension", SubstitutionWriter::GetLinkerSubstitution(
-                              &target, tool, &CSubstitutionOutputExtension));
+                              &target, tool, &SubstitutionOutputExtension));
   target.set_output_extension("");
   EXPECT_EQ("", SubstitutionWriter::GetLinkerSubstitution(
-                    &target, tool, &CSubstitutionOutputExtension));
+                    &target, tool, &SubstitutionOutputExtension));
 
   // Output directory is tested in a separate test below.
 }
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 5b3d603..db7a1be 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -311,6 +311,8 @@
       return functions::kCreateBundle;
     case GENERATED_FILE:
       return functions::kGeneratedFile;
+    case RUST_LIBRARY:
+      return functions::kRustLibrary;
     default:
       return "";
   }
diff --git a/tools/gn/test_with_scope.cc b/tools/gn/test_with_scope.cc
index 1b9f9f4..477f35d 100644
--- a/tools/gn/test_with_scope.cc
+++ b/tools/gn/test_with_scope.cc
@@ -195,23 +195,81 @@
   toolchain->SetTool(std::move(compile_xcassets_tool));
 
   // RUST
-  std::unique_ptr<Tool> rustc_tool = Tool::CreateTool(RustTool::kRsToolRustc);
+  std::unique_ptr<Tool> rustc_tool = Tool::CreateTool(RustTool::kRsToolBin);
   SetCommandForTool(
       "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
-      "--crate-type {{crate_type}} {{rustflags}} -o "
-      "{{target_out_dir}}/"
-      "{{rustc_output_prefix}}{{crate_name}}{{rustc_output_extension}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
       "{{rustdeps}} {{externs}}",
       rustc_tool.get());
-  rustc_tool->AsRust()->set_dylib_output_extension(".so");
-  rustc_tool->AsRust()->set_cdylib_output_extension(".so");
-  rustc_tool->AsRust()->set_staticlib_output_extension(".a");
-  rustc_tool->AsRust()->set_proc_macro_output_extension(".so");
-  rustc_tool->AsRust()->set_outputs(SubstitutionList::MakeForTest(
-      "{{target_out_dir}}/"
-      "{{rustc_output_prefix}}{{crate_name}}{{rustc_output_extension}}"));
+  rustc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_out_dir}}/{{crate_name}}{{output_extension}}"));
   toolchain->SetTool(std::move(rustc_tool));
 
+  // CDYLIB
+  std::unique_ptr<Tool> cdylib_tool = Tool::CreateTool(RustTool::kRsToolCDylib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      cdylib_tool.get());
+  cdylib_tool->set_output_prefix("lib");
+  cdylib_tool->set_default_output_extension(".so");
+  cdylib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_output_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(cdylib_tool));
+
+  // DYLIB
+  std::unique_ptr<Tool> dylib_tool = Tool::CreateTool(RustTool::kRsToolDylib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      dylib_tool.get());
+  dylib_tool->set_output_prefix("lib");
+  dylib_tool->set_default_output_extension(".so");
+  dylib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_output_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(dylib_tool));
+
+  // PROC_MACRO
+  std::unique_ptr<Tool> proc_macro_tool = Tool::CreateTool(RustTool::kRsToolMacro);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      proc_macro_tool.get());
+  proc_macro_tool->set_output_prefix("lib");
+  proc_macro_tool->set_default_output_extension(".so");
+  proc_macro_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(proc_macro_tool));
+
+  // RLIB
+  std::unique_ptr<Tool> rlib_tool = Tool::CreateTool(RustTool::kRsToolRlib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      rlib_tool.get());
+  rlib_tool->set_output_prefix("lib");
+  rlib_tool->set_default_output_extension(".rlib");
+  rlib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(rlib_tool));
+
+  // STATICLIB
+  std::unique_ptr<Tool> staticlib_tool = Tool::CreateTool(RustTool::kRsToolStaticlib);
+  SetCommandForTool(
+      "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} "
+      "--crate-type {{crate_type}} {{rustflags}} -o {{output}} "
+      "{{rustdeps}} {{externs}}",
+      staticlib_tool.get());
+  staticlib_tool->set_output_prefix("lib");
+  staticlib_tool->set_default_output_extension(".a");
+  staticlib_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
+  toolchain->SetTool(std::move(staticlib_tool));
+
   toolchain->ToolchainSetupComplete();
 }
 
diff --git a/tools/gn/tool.cc b/tools/gn/tool.cc
index d67933e..43f6fbd 100644
--- a/tools/gn/tool.cc
+++ b/tools/gn/tool.cc
@@ -274,8 +274,18 @@
         GeneralTool::kGeneralToolCompileXCAssets);
 
   // Rust tool
-  else if (name == RustTool::kRsToolRustc)
-    return std::make_unique<RustTool>(RustTool::kRsToolRustc);
+  else if (name == RustTool::kRsToolBin)
+    return std::make_unique<RustTool>(RustTool::kRsToolBin);
+  else if (name == RustTool::kRsToolCDylib)
+    return std::make_unique<RustTool>(RustTool::kRsToolCDylib);
+  else if (name == RustTool::kRsToolDylib)
+    return std::make_unique<RustTool>(RustTool::kRsToolDylib);
+  else if (name == RustTool::kRsToolMacro)
+    return std::make_unique<RustTool>(RustTool::kRsToolMacro);
+  else if (name == RustTool::kRsToolRlib)
+    return std::make_unique<RustTool>(RustTool::kRsToolRlib);
+  else if (name == RustTool::kRsToolStaticlib)
+    return std::make_unique<RustTool>(RustTool::kRsToolStaticlib);
 
   return nullptr;
 }
@@ -297,7 +307,7 @@
     case SourceFile::SOURCE_RC:
       return CTool::kCToolRc;
     case SourceFile::SOURCE_RS:
-      return RustTool::kRsToolRustc;
+      return RustTool::kRsToolBin;
     case SourceFile::SOURCE_UNKNOWN:
     case SourceFile::SOURCE_H:
     case SourceFile::SOURCE_O:
@@ -315,8 +325,39 @@
   // The contents of this list might be suprising (i.e. stamp tool for copy
   // rules). See the header for why.
   // TODO(crbug.com/gn/39): Don't emit stamp files for single-output targets.
-  if (target->source_types_used().RustSourceUsed())
-    return RustTool::kRsToolRustc;
+  if (target->source_types_used().RustSourceUsed()) {
+    switch (target->rust_values().crate_type()) {
+      case RustValues::CRATE_AUTO: {
+        switch (target->output_type()) {
+          case Target::EXECUTABLE:
+            return RustTool::kRsToolBin;
+          case Target::SHARED_LIBRARY:
+            return RustTool::kRsToolDylib;
+          case Target::STATIC_LIBRARY:
+            return RustTool::kRsToolStaticlib;
+          case Target::RUST_LIBRARY:
+            return RustTool::kRsToolRlib;
+          default:
+            break;
+        }
+        break;
+      }
+      case RustValues::CRATE_BIN:
+        return RustTool::kRsToolBin;
+      case RustValues::CRATE_CDYLIB:
+        return RustTool::kRsToolCDylib;
+      case RustValues::CRATE_DYLIB:
+        return RustTool::kRsToolDylib;
+      case RustValues::CRATE_PROC_MACRO:
+        return RustTool::kRsToolMacro;
+      case RustValues::CRATE_RLIB:
+        return RustTool::kRsToolRlib;
+      case RustValues::CRATE_STATICLIB:
+        return RustTool::kRsToolStaticlib;
+      default:
+        NOTREACHED();
+    }
+  }
   switch (target->output_type()) {
     case Target::GROUP:
       return GeneralTool::kGeneralToolStamp;