• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "gn/rust_project_writer.h"
6 
7 #include <fstream>
8 #include <optional>
9 #include <sstream>
10 #include <tuple>
11 
12 #include "base/json/string_escape.h"
13 #include "base/strings/string_split.h"
14 #include "gn/builder.h"
15 #include "gn/deps_iterator.h"
16 #include "gn/ninja_target_command_util.h"
17 #include "gn/rust_project_writer_helpers.h"
18 #include "gn/rust_tool.h"
19 #include "gn/source_file.h"
20 #include "gn/string_output_buffer.h"
21 #include "gn/tool.h"
22 
23 #if defined(OS_WINDOWS)
24 #define NEWLINE "\r\n"
25 #else
26 #define NEWLINE "\n"
27 #endif
28 
29 // Current structure of rust-project.json output file
30 //
31 // {
32 //    "sysroot": "path/to/rust/sysroot",
33 //    "crates": [
34 //        {
35 //            "deps": [
36 //                {
37 //                    "crate": 1, // index into crate array
38 //                    "name": "alloc" // extern name of dependency
39 //                },
40 //            ],
41 //            "source": [
42 //                "include_dirs": [
43 //                     "some/source/root",
44 //                     "some/gen/dir",
45 //                ],
46 //                "exclude_dirs": []
47 //            },
48 //            "edition": "2021", // edition of crate
49 //            "cfg": [
50 //              "unix", // "atomic" value config options
51 //              "rust_panic=\"abort\""", // key="value" config options
52 //            ]
53 //            "root_module": "absolute path to crate",
54 //            "label": "//path/target:value", // GN target for the crate
55 //            "target": "x86_64-unknown-linux" // optional rustc target
56 //        },
57 // }
58 //
59 
RunAndWriteFiles(const BuildSettings * build_settings,const Builder & builder,const std::string & file_name,bool quiet,Err * err)60 bool RustProjectWriter::RunAndWriteFiles(const BuildSettings* build_settings,
61                                          const Builder& builder,
62                                          const std::string& file_name,
63                                          bool quiet,
64                                          Err* err) {
65   SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
66       Value(nullptr, file_name), err);
67   if (output_file.is_null())
68     return false;
69 
70   base::FilePath output_path = build_settings->GetFullPath(output_file);
71 
72   std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
73 
74   StringOutputBuffer out_buffer;
75   std::ostream out(&out_buffer);
76 
77   RenderJSON(build_settings, all_targets, out);
78   return out_buffer.WriteToFileIfChanged(output_path, err);
79 }
80 
81 // Map of Targets to their index in the crates list (for linking dependencies to
82 // their indexes).
83 using TargetIndexMap = std::unordered_map<const Target*, uint32_t>;
84 
85 // A collection of Targets.
86 using TargetsVector = UniqueVector<const Target*>;
87 
88 // Get the Rust deps for a target, recursively expanding OutputType::GROUPS
89 // that are present in the GN structure.  This will return a flattened list of
90 // deps from the groups, but will not expand a Rust lib dependency to find any
91 // transitive Rust dependencies.
GetRustDeps(const Target * target,TargetsVector * rust_deps)92 void GetRustDeps(const Target* target, TargetsVector* rust_deps) {
93   for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
94     const Target* dep = pair.ptr;
95 
96     if (dep->source_types_used().RustSourceUsed()) {
97       // Include any Rust dep.
98       rust_deps->push_back(dep);
99     } else if (dep->output_type() == Target::OutputType::GROUP) {
100       // Inspect (recursively) any group to see if it contains Rust deps.
101       GetRustDeps(dep, rust_deps);
102     }
103   }
104 }
GetRustDeps(const Target * target)105 TargetsVector GetRustDeps(const Target* target) {
106   TargetsVector deps;
107   GetRustDeps(target, &deps);
108   return deps;
109 }
110 
ExtractCompilerArgs(const Target * target)111 std::vector<std::string> ExtractCompilerArgs(const Target* target) {
112   std::vector<std::string> args;
113   for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
114     auto rustflags = iter.cur().rustflags();
115     for (auto flag_iter = rustflags.begin(); flag_iter != rustflags.end();
116          flag_iter++) {
117       args.push_back(*flag_iter);
118     }
119   }
120   return args;
121 }
122 
FindArgValue(const char * arg,const std::vector<std::string> & args)123 std::optional<std::string> FindArgValue(const char* arg,
124                                         const std::vector<std::string>& args) {
125   for (auto current = args.begin(); current != args.end();) {
126     // capture the current value
127     auto previous = *current;
128     // and increment
129     current++;
130 
131     // if the previous argument matches `arg`, and after the above increment the
132     // end hasn't been reached, this current argument is the desired value.
133     if (previous == arg && current != args.end()) {
134       return std::make_optional(*current);
135     }
136   }
137   return std::nullopt;
138 }
139 
FindArgValueAfterPrefix(const std::string & prefix,const std::vector<std::string> & args)140 std::optional<std::string> FindArgValueAfterPrefix(
141     const std::string& prefix,
142     const std::vector<std::string>& args) {
143   for (auto arg : args) {
144     if (!arg.compare(0, prefix.size(), prefix)) {
145       auto value = arg.substr(prefix.size());
146       return std::make_optional(value);
147     }
148   }
149   return std::nullopt;
150 }
151 
FindAllArgValuesAfterPrefix(const std::string & prefix,const std::vector<std::string> & args)152 std::vector<std::string> FindAllArgValuesAfterPrefix(
153     const std::string& prefix,
154     const std::vector<std::string>& args) {
155   std::vector<std::string> values;
156   for (auto arg : args) {
157     if (!arg.compare(0, prefix.size(), prefix)) {
158       auto value = arg.substr(prefix.size());
159       values.push_back(value);
160     }
161   }
162   return values;
163 }
164 
AddTarget(const BuildSettings * build_settings,const Target * target,TargetIndexMap & lookup,CrateList & crate_list)165 void AddTarget(const BuildSettings* build_settings,
166                const Target* target,
167                TargetIndexMap& lookup,
168                CrateList& crate_list) {
169   if (lookup.find(target) != lookup.end()) {
170     // If target is already in the lookup, we don't add it again.
171     return;
172   }
173 
174   auto compiler_args = ExtractCompilerArgs(target);
175   auto compiler_target = FindArgValue("--target", compiler_args);
176   auto crate_deps = GetRustDeps(target);
177 
178   // Add all dependencies of this crate, before this crate.
179   for (const auto& dep : crate_deps) {
180     AddTarget(build_settings, dep, lookup, crate_list);
181   }
182 
183   // The index of a crate is its position (0-based) in the list of crates.
184   size_t crate_id = crate_list.size();
185 
186   // Add the target to the crate lookup.
187   lookup.insert(std::make_pair(target, crate_id));
188 
189   SourceFile crate_root = target->rust_values().crate_root();
190   std::string crate_label = target->label().GetUserVisibleName(false);
191 
192   auto edition =
193       FindArgValueAfterPrefix(std::string("--edition="), compiler_args);
194   if (!edition.has_value()) {
195     edition = FindArgValue("--edition", compiler_args);
196   }
197 
198   auto gen_dir = GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN);
199 
200   Crate crate = Crate(crate_root, gen_dir, crate_id, crate_label,
201                       edition.value_or("2015"));
202 
203   crate.SetCompilerArgs(compiler_args);
204   if (compiler_target.has_value())
205     crate.SetCompilerTarget(compiler_target.value());
206 
207   ConfigList cfgs =
208       FindAllArgValuesAfterPrefix(std::string("--cfg="), compiler_args);
209 
210   crate.AddConfigItem("test");
211   crate.AddConfigItem("debug_assertions");
212 
213   for (auto& cfg : cfgs) {
214     crate.AddConfigItem(cfg);
215   }
216 
217   // If it's a proc macro, record its output location so IDEs can invoke it.
218   auto rust_tool =
219       target->toolchain()->GetToolForTargetFinalOutputAsRust(target);
220   if (std::string_view(rust_tool->name()) ==
221       std::string_view(RustTool::kRsToolMacro)) {
222     auto outputs = target->computed_outputs();
223     if (outputs.size() > 0) {
224       crate.SetIsProcMacro(outputs[0]);
225     }
226   }
227 
228   // Note any environment variables. These may be used by proc macros
229   // invoked by the current crate (so we want to record these for all crates,
230   // not just proc macro crates)
231   for (const auto& env_var : target->config_values().rustenv()) {
232     std::vector<std::string> parts = base::SplitString(
233         env_var, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
234     if (parts.size() >= 2) {
235       crate.AddRustenv(parts[0], parts[1]);
236     }
237   }
238 
239   // Add the rest of the crate dependencies.
240   for (const auto& dep : crate_deps) {
241     auto idx = lookup[dep];
242     crate.AddDependency(idx, dep->rust_values().crate_name());
243   }
244 
245   crate_list.push_back(crate);
246 }
247 
WriteCrates(const BuildSettings * build_settings,CrateList & crate_list,std::optional<std::string> & sysroot,std::ostream & rust_project)248 void WriteCrates(const BuildSettings* build_settings,
249                  CrateList& crate_list,
250                  std::optional<std::string>& sysroot,
251                  std::ostream& rust_project) {
252   rust_project << "{" NEWLINE;
253 
254   // If a sysroot was found, then that can be used to tell rust-analyzer where
255   // to find the sysroot (and associated tools like the
256   // 'rust-analyzer-proc-macro-srv` proc-macro server that matches the abi used
257   // by 'rustc'
258   if (sysroot.has_value()) {
259     base::FilePath rebased_out_dir =
260         build_settings->GetFullPath(build_settings->build_dir());
261     auto sysroot_path = FilePathToUTF8(rebased_out_dir) + sysroot.value();
262     rust_project << "  \"sysroot\": \"" << sysroot_path << "\"," NEWLINE;
263   }
264 
265   rust_project << "  \"crates\": [";
266   bool first_crate = true;
267   for (auto& crate : crate_list) {
268     if (!first_crate)
269       rust_project << ",";
270     first_crate = false;
271 
272     auto crate_module =
273         FilePathToUTF8(build_settings->GetFullPath(crate.root()));
274 
275     rust_project << NEWLINE << "    {" NEWLINE
276                  << "      \"crate_id\": " << crate.index() << "," NEWLINE
277                  << "      \"root_module\": \"" << crate_module << "\"," NEWLINE
278                  << "      \"label\": \"" << crate.label() << "\"," NEWLINE
279                  << "      \"source\": {" NEWLINE
280                  << "          \"include_dirs\": [" NEWLINE
281                  << "               \""
282                  << FilePathToUTF8(
283                         build_settings->GetFullPath(crate.root().GetDir()))
284                  << "\"";
285     auto gen_dir = crate.gen_dir();
286     if (gen_dir.has_value()) {
287       auto gen_dir_path = FilePathToUTF8(
288           build_settings->GetFullPath(gen_dir->AsSourceDir(build_settings)));
289       rust_project << "," NEWLINE << "               \"" << gen_dir_path
290                    << "\"" NEWLINE;
291     } else {
292       rust_project << NEWLINE;
293     }
294     rust_project << "          ]," NEWLINE
295                  << "          \"exclude_dirs\": []" NEWLINE
296                  << "      }," NEWLINE;
297 
298     auto compiler_target = crate.CompilerTarget();
299     if (compiler_target.has_value()) {
300       rust_project << "      \"target\": \"" << compiler_target.value()
301                    << "\"," NEWLINE;
302     }
303 
304     auto compiler_args = crate.CompilerArgs();
305     if (!compiler_args.empty()) {
306       rust_project << "      \"compiler_args\": [";
307       bool first_arg = true;
308       for (auto& arg : crate.CompilerArgs()) {
309         if (!first_arg)
310           rust_project << ", ";
311         first_arg = false;
312 
313         std::string escaped_arg;
314         base::EscapeJSONString(arg, false, &escaped_arg);
315 
316         rust_project << "\"" << escaped_arg << "\"";
317       }
318       rust_project << "]," << NEWLINE;
319     }
320 
321     rust_project << "      \"deps\": [";
322     bool first_dep = true;
323     for (auto& dep : crate.dependencies()) {
324       if (!first_dep)
325         rust_project << ",";
326       first_dep = false;
327 
328       rust_project << NEWLINE << "        {" NEWLINE
329                    << "          \"crate\": " << dep.first << "," NEWLINE
330                    << "          \"name\": \"" << dep.second << "\"" NEWLINE
331                    << "        }";
332     }
333     rust_project << NEWLINE "      ]," NEWLINE;  // end dep list
334 
335     rust_project << "      \"edition\": \"" << crate.edition() << "\"," NEWLINE;
336 
337     auto proc_macro_target = crate.proc_macro_path();
338     if (proc_macro_target.has_value()) {
339       rust_project << "      \"is_proc_macro\": true," NEWLINE;
340       auto so_location = FilePathToUTF8(build_settings->GetFullPath(
341           proc_macro_target->AsSourceFile(build_settings)));
342       rust_project << "      \"proc_macro_dylib_path\": \"" << so_location
343                    << "\"," NEWLINE;
344     }
345 
346     rust_project << "      \"cfg\": [";
347     bool first_cfg = true;
348     for (const auto& cfg : crate.configs()) {
349       if (!first_cfg)
350         rust_project << ",";
351       first_cfg = false;
352 
353       std::string escaped_config;
354       base::EscapeJSONString(cfg, false, &escaped_config);
355 
356       rust_project << NEWLINE;
357       rust_project << "        \"" << escaped_config << "\"";
358     }
359     rust_project << NEWLINE;
360     rust_project << "      ]";  // end cfgs
361 
362     if (!crate.rustenv().empty()) {
363       rust_project << "," NEWLINE;
364       rust_project << "      \"env\": {";
365       bool first_env = true;
366       for (const auto& env : crate.rustenv()) {
367         if (!first_env)
368           rust_project << ",";
369         first_env = false;
370         std::string escaped_key, escaped_val;
371         base::EscapeJSONString(env.first, false, &escaped_key);
372         base::EscapeJSONString(env.second, false, &escaped_val);
373         rust_project << NEWLINE;
374         rust_project << "        \"" << escaped_key << "\": \"" << escaped_val
375                      << "\"";
376       }
377 
378       rust_project << NEWLINE;
379       rust_project << "      }" NEWLINE;  // end env vars
380     } else {
381       rust_project << NEWLINE;
382     }
383     rust_project << "    }";  // end crate
384   }
385   rust_project << NEWLINE "  ]" NEWLINE;  // end crate list
386   rust_project << "}" NEWLINE;
387 }
388 
RenderJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets,std::ostream & rust_project)389 void RustProjectWriter::RenderJSON(const BuildSettings* build_settings,
390                                    std::vector<const Target*>& all_targets,
391                                    std::ostream& rust_project) {
392   TargetIndexMap lookup;
393   CrateList crate_list;
394   std::optional<std::string> rust_sysroot;
395 
396   // All the crates defined in the project.
397   for (const auto* target : all_targets) {
398     if (!target->IsBinary() || !target->source_types_used().RustSourceUsed())
399       continue;
400 
401     AddTarget(build_settings, target, lookup, crate_list);
402 
403     // If a sysroot hasn't been found, see if we can find one using this target.
404     if (!rust_sysroot.has_value()) {
405       auto rust_tool =
406           target->toolchain()->GetToolForTargetFinalOutputAsRust(target);
407       auto sysroot = rust_tool->GetSysroot();
408       if (sysroot != "")
409         rust_sysroot = sysroot;
410     }
411   }
412 
413   WriteCrates(build_settings, crate_list, rust_sysroot, rust_project);
414 }
415