• 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 //    "crates": [
33 //        {
34 //            "deps": [
35 //                {
36 //                    "crate": 1, // index into crate array
37 //                    "name": "alloc" // extern name of dependency
38 //                },
39 //            ],
40 //            "source": [
41 //                "include_dirs": [
42 //                     "some/source/root",
43 //                     "some/gen/dir",
44 //                ],
45 //                "exclude_dirs": []
46 //            },
47 //            "edition": "2018", // edition of crate
48 //            "cfg": [
49 //              "unix", // "atomic" value config options
50 //              "rust_panic=\"abort\""", // key="value" config options
51 //            ]
52 //            "root_module": "absolute path to crate",
53 //            "label": "//path/target:value", // GN target for the crate
54 //            "target": "x86_64-unknown-linux" // optional rustc target
55 //        },
56 // }
57 //
58 
RunAndWriteFiles(const BuildSettings * build_settings,const Builder & builder,const std::string & file_name,bool quiet,Err * err)59 bool RustProjectWriter::RunAndWriteFiles(const BuildSettings* build_settings,
60                                          const Builder& builder,
61                                          const std::string& file_name,
62                                          bool quiet,
63                                          Err* err) {
64   SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
65       Value(nullptr, file_name), err);
66   if (output_file.is_null())
67     return false;
68 
69   base::FilePath output_path = build_settings->GetFullPath(output_file);
70 
71   std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
72 
73   StringOutputBuffer out_buffer;
74   std::ostream out(&out_buffer);
75 
76   RenderJSON(build_settings, all_targets, out);
77   return out_buffer.WriteToFileIfChanged(output_path, err);
78 }
79 
80 // Map of Targets to their index in the crates list (for linking dependencies to
81 // their indexes).
82 using TargetIndexMap = std::unordered_map<const Target*, uint32_t>;
83 
84 // A collection of Targets.
85 using TargetsVector = UniqueVector<const Target*>;
86 
87 // Get the Rust deps for a target, recursively expanding OutputType::GROUPS
88 // that are present in the GN structure.  This will return a flattened list of
89 // deps from the groups, but will not expand a Rust lib dependency to find any
90 // transitive Rust dependencies.
GetRustDeps(const Target * target,TargetsVector * rust_deps)91 void GetRustDeps(const Target* target, TargetsVector* rust_deps) {
92   for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
93     const Target* dep = pair.ptr;
94 
95     if (dep->source_types_used().RustSourceUsed()) {
96       // Include any Rust dep.
97       rust_deps->push_back(dep);
98     } else if (dep->output_type() == Target::OutputType::GROUP) {
99       // Inspect (recursively) any group to see if it contains Rust deps.
100       GetRustDeps(dep, rust_deps);
101     }
102   }
103 }
GetRustDeps(const Target * target)104 TargetsVector GetRustDeps(const Target* target) {
105   TargetsVector deps;
106   GetRustDeps(target, &deps);
107   return deps;
108 }
109 
ExtractCompilerArgs(const Target * target)110 std::vector<std::string> ExtractCompilerArgs(const Target* target) {
111   std::vector<std::string> args;
112   for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
113     auto rustflags = iter.cur().rustflags();
114     for (auto flag_iter = rustflags.begin(); flag_iter != rustflags.end();
115          flag_iter++) {
116       args.push_back(*flag_iter);
117     }
118   }
119   return args;
120 }
121 
FindArgValue(const char * arg,const std::vector<std::string> & args)122 std::optional<std::string> FindArgValue(const char* arg,
123                                         const std::vector<std::string>& args) {
124   for (auto current = args.begin(); current != args.end();) {
125     // capture the current value
126     auto previous = *current;
127     // and increment
128     current++;
129 
130     // if the previous argument matches `arg`, and after the above increment the
131     // end hasn't been reached, this current argument is the desired value.
132     if (previous == arg && current != args.end()) {
133       return std::make_optional(*current);
134     }
135   }
136   return std::nullopt;
137 }
138 
FindArgValueAfterPrefix(const std::string & prefix,const std::vector<std::string> & args)139 std::optional<std::string> FindArgValueAfterPrefix(
140     const std::string& prefix,
141     const std::vector<std::string>& args) {
142   for (auto arg : args) {
143     if (!arg.compare(0, prefix.size(), prefix)) {
144       auto value = arg.substr(prefix.size());
145       return std::make_optional(value);
146     }
147   }
148   return std::nullopt;
149 }
150 
FindAllArgValuesAfterPrefix(const std::string & prefix,const std::vector<std::string> & args)151 std::vector<std::string> FindAllArgValuesAfterPrefix(
152     const std::string& prefix,
153     const std::vector<std::string>& args) {
154   std::vector<std::string> values;
155   for (auto arg : args) {
156     if (!arg.compare(0, prefix.size(), prefix)) {
157       auto value = arg.substr(prefix.size());
158       values.push_back(value);
159     }
160   }
161   return values;
162 }
163 
164 // TODO(bwb) Parse sysroot structure from toml files. This is fragile and
165 // might break if upstream changes the dependency structure.
166 const std::string_view sysroot_crates[] = {"std",
167                                            "core",
168                                            "alloc",
169                                            "panic_unwind",
170                                            "proc_macro",
171                                            "test",
172                                            "panic_abort",
173                                            "unwind"};
174 
175 // Multiple sysroot crates have dependenices on each other.  This provides a
176 // mechanism for specifying that in an extendible manner.
177 const std::unordered_map<std::string_view, std::vector<std::string_view>>
178     sysroot_deps_map = {{"alloc", {"core"}},
179                         {"std", {"alloc", "core", "panic_abort", "unwind"}}};
180 
181 // Add each of the crates a sysroot has, including their dependencies.
AddSysrootCrate(const BuildSettings * build_settings,std::string_view crate,std::string_view current_sysroot,SysrootCrateIndexMap & sysroot_crate_lookup,CrateList & crate_list)182 void AddSysrootCrate(const BuildSettings* build_settings,
183                      std::string_view crate,
184                      std::string_view current_sysroot,
185                      SysrootCrateIndexMap& sysroot_crate_lookup,
186                      CrateList& crate_list) {
187   if (sysroot_crate_lookup.find(crate) != sysroot_crate_lookup.end()) {
188     // If this sysroot crate is already in the lookup, we don't add it again.
189     return;
190   }
191 
192   // Add any crates that this sysroot crate depends on.
193   auto deps_lookup = sysroot_deps_map.find(crate);
194   if (deps_lookup != sysroot_deps_map.end()) {
195     auto deps = (*deps_lookup).second;
196     for (auto dep : deps) {
197       AddSysrootCrate(build_settings, dep, current_sysroot,
198                       sysroot_crate_lookup, crate_list);
199     }
200   }
201 
202   size_t crate_index = crate_list.size();
203   sysroot_crate_lookup.insert(std::make_pair(crate, crate_index));
204 
205   base::FilePath rebased_out_dir =
206       build_settings->GetFullPath(build_settings->build_dir());
207   auto crate_path =
208       FilePathToUTF8(rebased_out_dir) + std::string(current_sysroot) +
209       "/lib/rustlib/src/rust/library/" + std::string(crate) + "/src/lib.rs";
210 
211   Crate sysroot_crate = Crate(SourceFile(crate_path), std::nullopt, crate_index,
212                               std::string(crate), "2018");
213 
214   sysroot_crate.AddConfigItem("debug_assertions");
215 
216   if (deps_lookup != sysroot_deps_map.end()) {
217     auto deps = (*deps_lookup).second;
218     for (auto dep : deps) {
219       auto idx = sysroot_crate_lookup[dep];
220       sysroot_crate.AddDependency(idx, std::string(dep));
221     }
222   }
223 
224   crate_list.push_back(sysroot_crate);
225 }
226 
227 // Add the given sysroot to the project, if it hasn't already been added.
AddSysroot(const BuildSettings * build_settings,std::string_view sysroot,SysrootIndexMap & sysroot_lookup,CrateList & crate_list)228 void AddSysroot(const BuildSettings* build_settings,
229                 std::string_view sysroot,
230                 SysrootIndexMap& sysroot_lookup,
231                 CrateList& crate_list) {
232   // If this sysroot is already in the lookup, we don't add it again.
233   if (sysroot_lookup.find(sysroot) != sysroot_lookup.end()) {
234     return;
235   }
236 
237   // Otherwise, add all of its crates
238   for (auto crate : sysroot_crates) {
239     AddSysrootCrate(build_settings, crate, sysroot, sysroot_lookup[sysroot],
240                     crate_list);
241   }
242 }
243 
AddSysrootDependencyToCrate(Crate * crate,const SysrootCrateIndexMap & sysroot,std::string_view crate_name)244 void AddSysrootDependencyToCrate(Crate* crate,
245                                  const SysrootCrateIndexMap& sysroot,
246                                  std::string_view crate_name) {
247   if (const auto crate_idx = sysroot.find(crate_name);
248       crate_idx != sysroot.end()) {
249     crate->AddDependency(crate_idx->second, std::string(crate_name));
250   }
251 }
252 
AddTarget(const BuildSettings * build_settings,const Target * target,TargetIndexMap & lookup,SysrootIndexMap & sysroot_lookup,CrateList & crate_list)253 void AddTarget(const BuildSettings* build_settings,
254                const Target* target,
255                TargetIndexMap& lookup,
256                SysrootIndexMap& sysroot_lookup,
257                CrateList& crate_list) {
258   if (lookup.find(target) != lookup.end()) {
259     // If target is already in the lookup, we don't add it again.
260     return;
261   }
262 
263   auto compiler_args = ExtractCompilerArgs(target);
264   auto compiler_target = FindArgValue("--target", compiler_args);
265 
266   // Check what sysroot this target needs.  Add it to the crate list if it
267   // hasn't already been added.
268   auto rust_tool =
269       target->toolchain()->GetToolForTargetFinalOutputAsRust(target);
270   auto current_sysroot = rust_tool->GetSysroot();
271   if (current_sysroot != "" && sysroot_lookup.count(current_sysroot) == 0) {
272     AddSysroot(build_settings, current_sysroot, sysroot_lookup, crate_list);
273   }
274 
275   auto crate_deps = GetRustDeps(target);
276 
277   // Add all dependencies of this crate, before this crate.
278   for (const auto& dep : crate_deps) {
279     AddTarget(build_settings, dep, lookup, sysroot_lookup, crate_list);
280   }
281 
282   // The index of a crate is its position (0-based) in the list of crates.
283   size_t crate_id = crate_list.size();
284 
285   // Add the target to the crate lookup.
286   lookup.insert(std::make_pair(target, crate_id));
287 
288   SourceFile crate_root = target->rust_values().crate_root();
289   std::string crate_label = target->label().GetUserVisibleName(false);
290 
291   auto edition =
292       FindArgValueAfterPrefix(std::string("--edition="), compiler_args);
293   if (!edition.has_value()) {
294     edition = FindArgValue("--edition", compiler_args);
295   }
296 
297   auto gen_dir = GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN);
298 
299   Crate crate = Crate(crate_root, gen_dir, crate_id, crate_label,
300                       edition.value_or("2015"));
301 
302   crate.SetCompilerArgs(compiler_args);
303   if (compiler_target.has_value())
304     crate.SetCompilerTarget(compiler_target.value());
305 
306   ConfigList cfgs =
307       FindAllArgValuesAfterPrefix(std::string("--cfg="), compiler_args);
308 
309   crate.AddConfigItem("test");
310   crate.AddConfigItem("debug_assertions");
311 
312   for (auto& cfg : cfgs) {
313     crate.AddConfigItem(cfg);
314   }
315 
316   // Add the sysroot dependencies, if there is one.
317   if (current_sysroot != "") {
318     const auto& sysroot = sysroot_lookup[current_sysroot];
319     AddSysrootDependencyToCrate(&crate, sysroot, "core");
320     AddSysrootDependencyToCrate(&crate, sysroot, "alloc");
321     AddSysrootDependencyToCrate(&crate, sysroot, "std");
322 
323     // Proc macros have the proc_macro crate as a direct dependency
324     if (std::string_view(rust_tool->name()) ==
325         std::string_view(RustTool::kRsToolMacro)) {
326       AddSysrootDependencyToCrate(&crate, sysroot, "proc_macro");
327     }
328   }
329 
330   // If it's a proc macro, record its output location so IDEs can invoke it.
331   if (std::string_view(rust_tool->name()) ==
332       std::string_view(RustTool::kRsToolMacro)) {
333     auto outputs = target->computed_outputs();
334     if (outputs.size() > 0) {
335       crate.SetIsProcMacro(outputs[0]);
336     }
337   }
338 
339   // Note any environment variables. These may be used by proc macros
340   // invoked by the current crate (so we want to record these for all crates,
341   // not just proc macro crates)
342   for (const auto& env_var : target->config_values().rustenv()) {
343     std::vector<std::string> parts = base::SplitString(
344         env_var, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
345     if (parts.size() >= 2) {
346       crate.AddRustenv(parts[0], parts[1]);
347     }
348   }
349 
350   // Add the rest of the crate dependencies.
351   for (const auto& dep : crate_deps) {
352     auto idx = lookup[dep];
353     crate.AddDependency(idx, dep->rust_values().crate_name());
354   }
355 
356   crate_list.push_back(crate);
357 }
358 
WriteCrates(const BuildSettings * build_settings,CrateList & crate_list,std::ostream & rust_project)359 void WriteCrates(const BuildSettings* build_settings,
360                  CrateList& crate_list,
361                  std::ostream& rust_project) {
362   rust_project << "{" NEWLINE;
363   rust_project << "  \"crates\": [";
364   bool first_crate = true;
365   for (auto& crate : crate_list) {
366     if (!first_crate)
367       rust_project << ",";
368     first_crate = false;
369 
370     auto crate_module =
371         FilePathToUTF8(build_settings->GetFullPath(crate.root()));
372 
373     rust_project << NEWLINE << "    {" NEWLINE
374                  << "      \"crate_id\": " << crate.index() << "," NEWLINE
375                  << "      \"root_module\": \"" << crate_module << "\"," NEWLINE
376                  << "      \"label\": \"" << crate.label() << "\"," NEWLINE
377                  << "      \"source\": {" NEWLINE
378                  << "          \"include_dirs\": [" NEWLINE
379                  << "               \""
380                  << FilePathToUTF8(
381                         build_settings->GetFullPath(crate.root().GetDir()))
382                  << "\"";
383     auto gen_dir = crate.gen_dir();
384     if (gen_dir.has_value()) {
385       auto gen_dir_path = FilePathToUTF8(
386           build_settings->GetFullPath(gen_dir->AsSourceDir(build_settings)));
387       rust_project << "," NEWLINE << "               \"" << gen_dir_path
388                    << "\"" NEWLINE;
389     } else {
390       rust_project << NEWLINE;
391     }
392     rust_project << "          ]," NEWLINE
393                  << "          \"exclude_dirs\": []" NEWLINE
394                  << "      }," NEWLINE;
395 
396     auto compiler_target = crate.CompilerTarget();
397     if (compiler_target.has_value()) {
398       rust_project << "      \"target\": \"" << compiler_target.value()
399                    << "\"," NEWLINE;
400     }
401 
402     auto compiler_args = crate.CompilerArgs();
403     if (!compiler_args.empty()) {
404       rust_project << "      \"compiler_args\": [";
405       bool first_arg = true;
406       for (auto& arg : crate.CompilerArgs()) {
407         if (!first_arg)
408           rust_project << ", ";
409         first_arg = false;
410 
411         std::string escaped_arg;
412         base::EscapeJSONString(arg, false, &escaped_arg);
413 
414         rust_project << "\"" << escaped_arg << "\"";
415       }
416       rust_project << "]," << NEWLINE;
417     }
418 
419     rust_project << "      \"deps\": [";
420     bool first_dep = true;
421     for (auto& dep : crate.dependencies()) {
422       if (!first_dep)
423         rust_project << ",";
424       first_dep = false;
425 
426       rust_project << NEWLINE << "        {" NEWLINE
427                    << "          \"crate\": " << dep.first << "," NEWLINE
428                    << "          \"name\": \"" << dep.second << "\"" NEWLINE
429                    << "        }";
430     }
431     rust_project << NEWLINE "      ]," NEWLINE;  // end dep list
432 
433     rust_project << "      \"edition\": \"" << crate.edition() << "\"," NEWLINE;
434 
435     auto proc_macro_target = crate.proc_macro_path();
436     if (proc_macro_target.has_value()) {
437       rust_project << "      \"is_proc_macro\": true," NEWLINE;
438       auto so_location = FilePathToUTF8(build_settings->GetFullPath(
439           proc_macro_target->AsSourceFile(build_settings)));
440       rust_project << "      \"proc_macro_dylib_path\": \"" << so_location
441                    << "\"," NEWLINE;
442     }
443 
444     rust_project << "      \"cfg\": [";
445     bool first_cfg = true;
446     for (const auto& cfg : crate.configs()) {
447       if (!first_cfg)
448         rust_project << ",";
449       first_cfg = false;
450 
451       std::string escaped_config;
452       base::EscapeJSONString(cfg, false, &escaped_config);
453 
454       rust_project << NEWLINE;
455       rust_project << "        \"" << escaped_config << "\"";
456     }
457     rust_project << NEWLINE;
458     rust_project << "      ]";  // end cfgs
459 
460     if (!crate.rustenv().empty()) {
461       rust_project << "," NEWLINE;
462       rust_project << "      \"env\": {";
463       bool first_env = true;
464       for (const auto& env : crate.rustenv()) {
465         if (!first_env)
466           rust_project << ",";
467         first_env = false;
468         std::string escaped_key, escaped_val;
469         base::EscapeJSONString(env.first, false, &escaped_key);
470         base::EscapeJSONString(env.second, false, &escaped_val);
471         rust_project << NEWLINE;
472         rust_project << "        \"" << escaped_key << "\": \"" << escaped_val
473                      << "\"";
474       }
475 
476       rust_project << NEWLINE;
477       rust_project << "      }" NEWLINE;  // end env vars
478     } else {
479       rust_project << NEWLINE;
480     }
481     rust_project << "    }";  // end crate
482   }
483   rust_project << NEWLINE "  ]" NEWLINE;  // end crate list
484   rust_project << "}" NEWLINE;
485 }
486 
RenderJSON(const BuildSettings * build_settings,std::vector<const Target * > & all_targets,std::ostream & rust_project)487 void RustProjectWriter::RenderJSON(const BuildSettings* build_settings,
488                                    std::vector<const Target*>& all_targets,
489                                    std::ostream& rust_project) {
490   TargetIndexMap lookup;
491   SysrootIndexMap sysroot_lookup;
492   CrateList crate_list;
493 
494   // All the crates defined in the project.
495   for (const auto* target : all_targets) {
496     if (!target->IsBinary() || !target->source_types_used().RustSourceUsed())
497       continue;
498 
499     AddTarget(build_settings, target, lookup, sysroot_lookup, crate_list);
500   }
501 
502   WriteCrates(build_settings, crate_list, rust_project);
503 }
504