[rust-project] Support space-separated --cfg arguments

When GN exports `rust-project.json` (via `--export-rust-project`), it
attempts to extract `cfg` arguments from a target's `rustflags` to
populate the `cfg` array in the JSON file. This allows language servers
like `rust-analyzer` to correctly resolve conditional compilation blocks
(e.g. `#[gtest]`).

Prior to this commit, GN only successfully extracts these elements if
they are specified using the single-string format (e.g., `rustflags =
[ "--cfg=foo" ]`). If the build configuration provides the flag and the
argument separately (e.g., `rustflags = [ "--cfg", "foo" ]`), GN ignores
the argument entirely. This results in `rust-analyzer` being unable to
resolve `cfg`-gated code in projects using the space-separated format.

Examples of using the space-separated syntax can be found for example in
Chromium in `build/rust/BUILD.gn`.

Change-Id: I2c99c0645f6052c16c98f557b26ef47c6a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23740
Reviewed-by: Andrew Grieve <agrieve@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Zgroza (Luke) Klimek <zgroza@chromium.org>
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
index 181d50f..eb2d4e2 100644
--- a/src/gn/rust_project_writer.cc
+++ b/src/gn/rust_project_writer.cc
@@ -137,6 +137,22 @@
   return std::nullopt;
 }
 
+std::vector<std::string> FindAllArgValues(
+    const char* arg,
+    const std::vector<std::string>& args) {
+  std::vector<std::string> values;
+  for (auto it = args.begin(); it != args.end(); ++it) {
+    if (*it == arg) {
+      ++it;
+      if (it == args.end()) {
+        break;
+      }
+      values.push_back(*it);
+    }
+  }
+  return values;
+}
+
 std::optional<std::string> FindArgValueAfterPrefix(
     const std::string& prefix,
     const std::vector<std::string>& args) {
@@ -231,6 +247,9 @@
     crate.SetCompilerTarget(compiler_target.value());
 
   ConfigList cfgs = FindAllArgValuesAfterPrefix("--cfg=", compiler_args);
+  auto exact_cfgs = FindAllArgValues("--cfg", compiler_args);
+  cfgs.insert(cfgs.end(), std::make_move_iterator(exact_cfgs.begin()),
+              std::make_move_iterator(exact_cfgs.end()));
 
   crate.AddConfigItem("test");
   crate.AddConfigItem("debug_assertions");
diff --git a/src/gn/rust_project_writer_helpers.h b/src/gn/rust_project_writer_helpers.h
index 4feff94..f11de8c 100644
--- a/src/gn/rust_project_writer_helpers.h
+++ b/src/gn/rust_project_writer_helpers.h
@@ -143,6 +143,11 @@
     const std::string& prefix,
     const std::vector<std::string>& args);
 
+// Find all arguments that match the given exact string, returning the value
+// after.
+std::vector<std::string> FindAllArgValues(const char* arg,
+                                          const std::vector<std::string>& args);
+
 // Find all arguments that match the given prefix, returning the value after
 // the prefix for each one.  e.g. "--cfg=value" is returned as "value" if the
 // prefix "--cfg=" is used.
diff --git a/src/gn/rust_project_writer_helpers_unittest.cc b/src/gn/rust_project_writer_helpers_unittest.cc
index c37f215..082e92e 100644
--- a/src/gn/rust_project_writer_helpers_unittest.cc
+++ b/src/gn/rust_project_writer_helpers_unittest.cc
@@ -252,3 +252,28 @@
                                        "feature=\"bar_enabled\""};
   EXPECT_EQ(expected, result);
 }
+
+TEST_F(RustProjectWriterHelper, FindAllArgValues) {
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  target.sources().push_back(lib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(lib);
+  target.rust_values().crate_name() = "foo";
+  target.config_values().rustflags().push_back("--cfg");
+  target.config_values().rustflags().push_back("feature=\"foo_enabled\"");
+  target.config_values().rustflags().push_back("--edition=2018");
+  target.config_values().rustflags().push_back("--cfg");
+  target.config_values().rustflags().push_back("feature=\"bar_enabled\"");
+  target.config_values().rustflags().push_back("--target");
+
+  auto args = ExtractCompilerArgs(&target);
+  std::vector<std::string> result = FindAllArgValues("--cfg", args);
+  std::vector<std::string> expected = {"feature=\"foo_enabled\"",
+                                       "feature=\"bar_enabled\""};
+  EXPECT_EQ(expected, result);
+}