Allow crate type to be set on all Rust targets

There are situations where the output type of the target does not match
the crate type in the compilation command. For example when the "--test"
flag is passed, an executable is produced, but the crate type is the
type of the specific crate, which is not necessarily 'bin'.

Change-Id: I7e2efc3ace9cbbc2ade838685967f7d26a2b21e6
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/5440
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/tools/gn/functions_target_rust_unittest.cc b/tools/gn/functions_target_rust_unittest.cc
index a8da82d..80e4696 100644
--- a/tools/gn/functions_target_rust_unittest.cc
+++ b/tools/gn/functions_target_rust_unittest.cc
@@ -172,20 +172,18 @@
   ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_type(),
             RustValues::CRATE_DYLIB);
 
-  TestParseInput exe_error_input(
+  TestParseInput exe_non_default_input(
       "executable(\"foo\") {\n"
-      "  crate_type = \"bin\"\n"
+      "  crate_type = \"rlib\"\n"
       "  sources = [ \"main.rs\" ]\n"
       "  edition = \"2018\""
       "}\n");
-  ASSERT_FALSE(exe_error_input.has_error());
+  ASSERT_FALSE(exe_non_default_input.has_error());
   err = Err();
-  exe_error_input.parsed()->Execute(setup.scope(), &err);
-  ASSERT_TRUE(err.has_error());
-  EXPECT_EQ(
-      "\"crate_type\" automatically inferred for non-shared Rust targets.",
-      err.message())
-      << err.message();
+  exe_non_default_input.parsed()->Execute(setup.scope(), &err);
+  ASSERT_FALSE(err.has_error()) << err.message();
+  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_type(),
+            RustValues::CRATE_RLIB);
 
   TestParseInput lib_error_input(
       "shared_library(\"foo\") {\n"
diff --git a/tools/gn/ninja_rust_binary_target_writer.cc b/tools/gn/ninja_rust_binary_target_writer.cc
index 3014df9..c39a207 100644
--- a/tools/gn/ninja_rust_binary_target_writer.cc
+++ b/tools/gn/ninja_rust_binary_target_writer.cc
@@ -40,30 +40,42 @@
            target->rust_values().crate_name(), opts, out);
 
   std::string crate_type;
-  switch (target->output_type()) {
-    case Target::EXECUTABLE:
-      crate_type = "bin";
-      break;
-    case Target::STATIC_LIBRARY:
-      crate_type = "staticlib";
-      break;
-    case Target::RUST_LIBRARY:
-      crate_type = "rlib";
-      break;
-    case Target::SHARED_LIBRARY:
-      switch (target->rust_values().crate_type()) {
-        case RustValues::CRATE_DYLIB:
-          crate_type = "dylib";
+  switch (target->rust_values().crate_type()) {
+    // Auto-select the crate type for executables, static libraries, and rlibs.
+    case RustValues::CRATE_AUTO: {
+      switch (target->output_type()) {
+        case Target::EXECUTABLE:
+          crate_type = "bin";
           break;
-        case RustValues::CRATE_CDYLIB:
-          crate_type = "cdylib";
+        case Target::STATIC_LIBRARY:
+          crate_type = "staticlib";
           break;
-        case RustValues::CRATE_PROC_MACRO:
-          crate_type = "proc-macro";
+        case Target::RUST_LIBRARY:
+          crate_type = "rlib";
           break;
         default:
           NOTREACHED();
       }
+      break;
+    }
+    case RustValues::CRATE_BIN:
+      crate_type = "bin";
+      break;
+    case RustValues::CRATE_CDYLIB:
+      crate_type = "cdylib";
+      break;
+    case RustValues::CRATE_DYLIB:
+      crate_type = "dylib";
+      break;
+    case RustValues::CRATE_PROC_MACRO:
+      crate_type = "proc-macro";
+      break;
+    case RustValues::CRATE_RLIB:
+      crate_type = "rlib";
+      break;
+    case RustValues::CRATE_STATICLIB:
+      crate_type = "staticlib";
+      break;
     default:
       NOTREACHED();
   }
diff --git a/tools/gn/rust_target_generator.cc b/tools/gn/rust_target_generator.cc
index 53d78cc..47dc0b6 100644
--- a/tools/gn/rust_target_generator.cc
+++ b/tools/gn/rust_target_generator.cc
@@ -14,6 +14,10 @@
 #include "tools/gn/target.h"
 #include "tools/gn/value_extractors.h"
 
+static const char* kRustSupportedCrateTypesError =
+    "\"crate_type\" must be one of \"bin\", \"cdylib\", \"dylib\", or "
+    "\"proc-macro\", \"rlib\", \"staticlib\".";
+
 RustTargetGenerator::RustTargetGenerator(Target* target,
                                          Scope* scope,
                                          const FunctionCallNode* function_call,
@@ -77,48 +81,50 @@
 bool RustTargetGenerator::FillCrateType() {
   const Value* value = scope_->GetValue(variables::kRustCrateType, true);
   if (!value) {
-    // Non-shared_library targets shouldn't set this, so that's okay.
-    if (target_->output_type() != Target::SHARED_LIBRARY &&
-        target_->output_type() != Target::LOADABLE_MODULE)
-      return true;
-    // But require shared_library and loadable_module targets to tell us what
+    // Require shared_library and loadable_module targets to tell us what
     // they want.
-    *err_ = Err(function_call_,
-                "Must set \"crate_type\" on a Rust \"shared_library\".",
-                "\"crate_type\" must be one of \"dylib\", \"cdylib\", or "
-                "\"proc-macro\".");
-    return false;
-  }
+    if (target_->output_type() == Target::SHARED_LIBRARY ||
+        target_->output_type() == Target::LOADABLE_MODULE) {
+      *err_ = Err(function_call_,
+                  "Must set \"crate_type\" on a Rust \"shared_library\".",
+                  kRustSupportedCrateTypesError);
+      return false;
+    }
 
-  if (target_->output_type() != Target::SHARED_LIBRARY &&
-      target_->output_type() != Target::LOADABLE_MODULE) {
-    *err_ = Err(
-        value->origin(),
-        "\"crate_type\" automatically inferred for non-shared Rust targets.",
-        "Setting it here has no effect.");
-    return false;
+    return true;
   }
 
   if (!value->VerifyTypeIs(Value::STRING, err_))
     return false;
 
-  if (value->string_value() == "dylib") {
-    target_->rust_values().set_crate_type(RustValues::CRATE_DYLIB);
+  if (value->string_value() == "bin") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_BIN);
     return true;
   }
   if (value->string_value() == "cdylib") {
     target_->rust_values().set_crate_type(RustValues::CRATE_CDYLIB);
     return true;
   }
+  if (value->string_value() == "dylib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_DYLIB);
+    return true;
+  }
   if (value->string_value() == "proc-macro") {
     target_->rust_values().set_crate_type(RustValues::CRATE_PROC_MACRO);
     return true;
   }
+  if (value->string_value() == "rlib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_RLIB);
+    return true;
+  }
+  if (value->string_value() == "staticlib") {
+    target_->rust_values().set_crate_type(RustValues::CRATE_STATICLIB);
+    return true;
+  }
 
   *err_ = Err(value->origin(),
               "Inadmissible crate type \"" + value->string_value() + "\".",
-              "\"crate_type\" must be one of \"dylib\", \"cdylib\", or "
-              "\"proc-macro\" for a \"shared_library\".");
+              kRustSupportedCrateTypesError);
   return false;
 }
 
@@ -189,8 +195,8 @@
       return false;
 
     // Insert into the aliased_deps map.
-    target_->rust_values().aliased_deps().emplace(
-        std::move(dep_label), pair.first.as_string());
+    target_->rust_values().aliased_deps().emplace(std::move(dep_label),
+                                                  pair.first.as_string());
   }
 
   return true;
diff --git a/tools/gn/rust_values.h b/tools/gn/rust_values.h
index a4762fb..ad9c5c6 100644
--- a/tools/gn/rust_values.h
+++ b/tools/gn/rust_values.h
@@ -18,14 +18,18 @@
   RustValues();
   ~RustValues();
 
-  // Shared library crate types are specified here, all other crate types are
-  // automatically deduced from the target type (e.g. executables use crate_type
-  // = "bin", static_libraries use crate_type = "staticlib").
+  // Library crate types are specified here. Shared library crate types must be
+  // specified, all other crate types can be automatically deduced from the
+  // target type (e.g. executables use crate_type = "bin", static_libraries use
+  // crate_type = "staticlib") unless explicitly set.
   enum CrateType {
     CRATE_AUTO = 0,
-    CRATE_DYLIB,
+    CRATE_BIN,
     CRATE_CDYLIB,
+    CRATE_DYLIB,
     CRATE_PROC_MACRO,
+    CRATE_RLIB,
+    CRATE_STATICLIB,
   };
 
   // Name of this crate.