• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2022 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Converts a cargo project to Soong.
16 //!
17 //! Forked from development/scripts/cargo2android.py. Missing many of its features. Adds various
18 //! features to make it easier to work with projects containing many crates.
19 //!
20 //! At a high level, this is done by
21 //!
22 //!     1. Running `cargo build -v` and saving the output to a "cargo.out" file.
23 //!     2. Parsing the "cargo.out" file to find invocations of compilers, e.g. `rustc` and `cc`.
24 //!     3. For each compiler invocation, generating a equivalent Soong module, e.g. a "rust_library".
25 //!
26 //! The last step often involves messy, project specific business logic, so many options are
27 //! available to tweak it via a config file.
28 
29 mod bp;
30 mod cargo_out;
31 
32 use anyhow::bail;
33 use anyhow::Context;
34 use anyhow::Result;
35 use bp::*;
36 use cargo_out::*;
37 use clap::Parser;
38 use once_cell::sync::Lazy;
39 use regex::Regex;
40 use std::collections::BTreeMap;
41 use std::collections::VecDeque;
42 use std::fs::File;
43 use std::io::Write;
44 use std::path::Path;
45 use std::path::PathBuf;
46 use std::process::Command;
47 
48 // Major TODOs
49 //  * handle errors, esp. in cargo.out parsing. they should fail the program with an error code
50 //  * handle warnings. put them in comments in the android.bp, some kind of report section
51 
52 #[derive(Parser, Debug)]
53 #[clap()]
54 struct Args {
55     /// Use the cargo binary in the `cargo_bin` directory. Defaults to cargo in $PATH.
56     ///
57     /// TODO: Should default to android prebuilts.
58     #[clap(long)]
59     cargo_bin: Option<PathBuf>,
60     /// Config file.
61     #[clap(long)]
62     cfg: PathBuf,
63     /// Skip the `cargo build` commands and reuse the "cargo.out" file from a previous run if
64     /// available.
65     #[clap(long)]
66     reuse_cargo_out: bool,
67 }
68 
default_apex_available() -> Vec<String>69 fn default_apex_available() -> Vec<String> {
70     vec!["//apex_available:platform".to_string(), "//apex_available:anyapex".to_string()]
71 }
72 
73 /// Options that apply to everything.
74 #[derive(serde::Deserialize)]
75 #[serde(deny_unknown_fields)]
76 struct Config {
77     /// Whether to output "rust_test" modules.
78     tests: bool,
79     /// Set of features to enable. If non-empty, disables the default crate features.
80     #[serde(default)]
81     features: Vec<String>,
82     /// Whether to build with --workspace.
83     #[serde(default)]
84     workspace: bool,
85     /// When workspace is enabled, list of --exclude crates.
86     #[serde(default)]
87     workspace_excludes: Vec<String>,
88     /// Value to use for every generated module's "defaults" field.
89     global_defaults: Option<String>,
90     /// Value to use for every generated library module's "apex_available" field.
91     #[serde(default = "default_apex_available")]
92     apex_available: Vec<String>,
93     /// Map of renames for modules. For example, if a "libfoo" would be generated and there is an
94     /// entry ("libfoo", "libbar"), the generated module will be called "libbar" instead.
95     ///
96     /// Also, affects references to dependencies (e.g. in a "static_libs" list), even those outside
97     /// the project being processed.
98     #[serde(default)]
99     module_name_overrides: BTreeMap<String, String>,
100     /// Package specific config options.
101     #[serde(default)]
102     package: BTreeMap<String, PackageConfig>,
103     /// Modules in this list will not be generated.
104     #[serde(default)]
105     module_blocklist: Vec<String>,
106     /// Modules name => Soong "visibility" property.
107     #[serde(default)]
108     module_visibility: BTreeMap<String, Vec<String>>,
109 }
110 
111 /// Options that apply to everything in a package (i.e. everything associated with a particular
112 /// Cargo.toml file).
113 #[derive(serde::Deserialize, Default)]
114 #[serde(deny_unknown_fields)]
115 struct PackageConfig {
116     /// Whether to compile for device. Defaults to true.
117     #[serde(default)]
118     device_supported: Option<bool>,
119     /// Whether to compile for host. Defaults to true.
120     #[serde(default)]
121     host_supported: Option<bool>,
122     /// Generate "rust_library_rlib" instead of "rust_library".
123     #[serde(default)]
124     force_rlib: bool,
125     /// Whether to disable "unit_test" for "rust_test" modules.
126     // TODO: Should probably be a list of modules or crates. A package might have a mix of unit and
127     // integration tests.
128     #[serde(default)]
129     no_presubmit: bool,
130     /// File with content to append to the end of the generated Android.bp.
131     add_toplevel_block: Option<PathBuf>,
132     /// File with content to append to the end of each generated module.
133     add_module_block: Option<PathBuf>,
134     /// Modules in this list will not be added as dependencies of generated modules.
135     #[serde(default)]
136     dep_blocklist: Vec<String>,
137     /// Patch file to apply after Android.bp is generated.
138     patch: Option<PathBuf>,
139     /// Copy build.rs output to ./out/* and add a genrule to copy ./out/* to genrule output.
140     /// For crates with code pattern:
141     ///     include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))
142     #[serde(default)]
143     copy_out: bool,
144 }
145 
main() -> Result<()>146 fn main() -> Result<()> {
147     let args = Args::parse();
148 
149     let json_str = std::fs::read_to_string(&args.cfg)
150         .with_context(|| format!("failed to read file: {:?}", args.cfg))?;
151     // Add some basic support for comments to JSON.
152     let json_str: String = json_str.lines().filter(|l| !l.trim_start().starts_with("//")).collect();
153     let cfg: Config = serde_json::from_str(&json_str).context("failed to parse config")?;
154 
155     if !Path::new("Cargo.toml").try_exists().context("when checking Cargo.toml")? {
156         bail!("Cargo.toml missing. Run in a directory with a Cargo.toml file.");
157     }
158 
159     // Add the custom cargo to PATH.
160     // NOTE: If the directory with cargo has more binaries, this could have some unpredictable side
161     // effects. That is partly intended though, because we want to use that cargo binary's
162     // associated rustc.
163     if let Some(cargo_bin) = args.cargo_bin {
164         let path = std::env::var_os("PATH").unwrap();
165         let mut paths = std::env::split_paths(&path).collect::<VecDeque<_>>();
166         paths.push_front(cargo_bin);
167         let new_path = std::env::join_paths(paths)?;
168         std::env::set_var("PATH", new_path);
169     }
170 
171     let cargo_out_path = "cargo.out";
172     let cargo_metadata_path = "cargo.metadata";
173     if !args.reuse_cargo_out || !Path::new(cargo_out_path).exists() {
174         generate_cargo_out(&cfg, cargo_out_path, cargo_metadata_path)
175             .context("generate_cargo_out failed")?;
176     }
177 
178     let crates =
179         parse_cargo_out(cargo_out_path, cargo_metadata_path).context("parse_cargo_out failed")?;
180 
181     // Find out files.
182     // Example: target.tmp/x86_64-unknown-linux-gnu/debug/build/metrics-d2dd799cebf1888d/out/event_details.rs
183     let mut package_out_files: BTreeMap<String, Vec<PathBuf>> = BTreeMap::new();
184     if cfg.package.iter().any(|(_, v)| v.copy_out) {
185         for entry in glob::glob("target.tmp/**/build/*/out/*")? {
186             match entry {
187                 Ok(path) => {
188                     let package_name = || -> Option<_> {
189                         let dir_name = path.parent()?.parent()?.file_name()?.to_str()?;
190                         Some(dir_name.rsplit_once('-')?.0)
191                     }()
192                     .unwrap_or_else(|| panic!("failed to parse out file path: {:?}", path));
193                     package_out_files
194                         .entry(package_name.to_string())
195                         .or_default()
196                         .push(path.clone());
197                 }
198                 Err(e) => eprintln!("failed to check for out files: {}", e),
199             }
200         }
201     }
202 
203     // Group by package.
204     let mut module_by_package: BTreeMap<PathBuf, Vec<Crate>> = BTreeMap::new();
205     for c in crates {
206         module_by_package.entry(c.package_dir.clone()).or_default().push(c);
207     }
208     // Write an Android.bp file per package.
209     for (package_dir, crates) in module_by_package {
210         write_android_bp(
211             &cfg,
212             package_dir,
213             &crates,
214             package_out_files.get(&crates[0].package_name),
215         )?;
216     }
217 
218     Ok(())
219 }
220 
run_cargo(cargo_out: &mut File, cmd: &mut Command) -> Result<()>221 fn run_cargo(cargo_out: &mut File, cmd: &mut Command) -> Result<()> {
222     use std::os::unix::io::OwnedFd;
223     use std::process::Stdio;
224     let fd: OwnedFd = cargo_out.try_clone()?.into();
225     // eprintln!("Running: {:?}\n", cmd);
226     let output = cmd.stdout(Stdio::from(fd.try_clone()?)).stderr(Stdio::from(fd)).output()?;
227     if !output.status.success() {
228         bail!("cargo command failed with exit status: {:?}", output.status);
229     }
230     Ok(())
231 }
232 
233 /// Run various cargo commands and save the output to `cargo_out_path`.
generate_cargo_out(cfg: &Config, cargo_out_path: &str, cargo_metadata_path: &str) -> Result<()>234 fn generate_cargo_out(cfg: &Config, cargo_out_path: &str, cargo_metadata_path: &str) -> Result<()> {
235     let mut cargo_out_file = std::fs::File::create(cargo_out_path)?;
236     let mut cargo_metadata_file = std::fs::File::create(cargo_metadata_path)?;
237 
238     let verbose_args = ["-v"];
239     let target_dir_args = ["--target-dir", "target.tmp"];
240 
241     // cargo clean
242     run_cargo(&mut cargo_out_file, Command::new("cargo").arg("clean").args(target_dir_args))?;
243 
244     let default_target = "x86_64-unknown-linux-gnu";
245     let feature_args = if cfg.features.is_empty() {
246         vec![]
247     } else {
248         vec!["--no-default-features".to_string(), "--features".to_string(), cfg.features.join(",")]
249     };
250 
251     let workspace_args = if cfg.workspace {
252         let mut v = vec!["--workspace".to_string()];
253         if !cfg.workspace_excludes.is_empty() {
254             for x in cfg.workspace_excludes.iter() {
255                 v.push("--exclude".to_string());
256                 v.push(x.clone());
257             }
258         }
259         v
260     } else {
261         vec![]
262     };
263 
264     // cargo metadata
265     run_cargo(
266         &mut cargo_metadata_file,
267         Command::new("cargo")
268             .arg("metadata")
269             .arg("-q") // don't output warnings to stderr
270             .arg("--format-version")
271             .arg("1")
272             .args(&feature_args),
273     )?;
274 
275     // cargo build
276     run_cargo(
277         &mut cargo_out_file,
278         Command::new("cargo")
279             .args(["build", "--target", default_target])
280             .args(verbose_args)
281             .args(target_dir_args)
282             .args(&workspace_args)
283             .args(&feature_args),
284     )?;
285 
286     if cfg.tests {
287         // cargo build --tests
288         run_cargo(
289             &mut cargo_out_file,
290             Command::new("cargo")
291                 .args(["build", "--target", default_target, "--tests"])
292                 .args(verbose_args)
293                 .args(target_dir_args)
294                 .args(&workspace_args)
295                 .args(&feature_args),
296         )?;
297     }
298 
299     Ok(())
300 }
301 
302 /// Create the Android.bp file for `package_dir`.
write_android_bp( cfg: &Config, package_dir: PathBuf, crates: &[Crate], out_files: Option<&Vec<PathBuf>>, ) -> Result<()>303 fn write_android_bp(
304     cfg: &Config,
305     package_dir: PathBuf,
306     crates: &[Crate],
307     out_files: Option<&Vec<PathBuf>>,
308 ) -> Result<()> {
309     let bp_path = package_dir.join("Android.bp");
310 
311     let package_name = crates[0].package_name.clone();
312     let def = PackageConfig::default();
313     let package_cfg = cfg.package.get(&package_name).unwrap_or(&def);
314 
315     // Keep the old license header.
316     let license_section = match std::fs::read_to_string(&bp_path) {
317         Ok(s) => s
318             .lines()
319             .skip_while(|l| l.starts_with("//"))
320             .take_while(|l| !l.starts_with("rust_") && !l.starts_with("genrule {"))
321             .collect::<Vec<&str>>()
322             .join("\n"),
323         Err(e) if e.kind() == std::io::ErrorKind::NotFound => "// TODO: Add license.\n".to_string(),
324         Err(e) => bail!("error when reading {bp_path:?}: {e}"),
325     };
326 
327     let mut bp_contents = String::new();
328     bp_contents += "// This file is generated by cargo_embargo.\n";
329     bp_contents += "// Do not modify this file as changes will be overridden on upgrade.\n\n";
330     bp_contents += license_section.trim();
331     bp_contents += "\n";
332 
333     let mut modules = Vec::new();
334 
335     let extra_srcs = match (package_cfg.copy_out, out_files) {
336         (true, Some(out_files)) => {
337             let out_dir = package_dir.join("out");
338             if !out_dir.exists() {
339                 std::fs::create_dir(&out_dir).expect("failed to create out dir");
340             }
341 
342             let mut outs: Vec<String> = Vec::new();
343             for f in out_files.iter() {
344                 let dest = out_dir.join(f.file_name().unwrap());
345                 std::fs::copy(f, dest).expect("failed to copy out file");
346                 outs.push(f.file_name().unwrap().to_str().unwrap().to_string());
347             }
348 
349             let mut m = BpModule::new("genrule".to_string());
350             let module_name = format!("copy_{}_build_out", package_name);
351             m.props.set("name", module_name.clone());
352             m.props.set("srcs", vec!["out/*"]);
353             m.props.set("cmd", "cp $(in) $(genDir)");
354             m.props.set("out", outs);
355             modules.push(m);
356 
357             vec![":".to_string() + &module_name]
358         }
359         _ => vec![],
360     };
361 
362     for c in crates {
363         modules.extend(crate_to_bp_modules(c, cfg, package_cfg, &extra_srcs).with_context(
364             || {
365                 format!(
366                     "failed to generate bp module for crate \"{}\" with package name \"{}\"",
367                     c.name, c.package_name
368                 )
369             },
370         )?);
371     }
372     if modules.is_empty() {
373         return Ok(());
374     }
375 
376     modules.sort_by_key(|m| m.props.get_string("name").to_string());
377     for m in modules {
378         m.write(&mut bp_contents)?;
379         bp_contents += "\n";
380     }
381     if let Some(path) = &package_cfg.add_toplevel_block {
382         bp_contents +=
383             &std::fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
384         bp_contents += "\n";
385     }
386     File::create(&bp_path)?.write_all(bp_contents.as_bytes())?;
387 
388     let bpfmt_output = Command::new("bpfmt").arg("-w").arg(&bp_path).output()?;
389     if !bpfmt_output.status.success() {
390         eprintln!(
391             "WARNING: bpfmt -w {:?} failed before patch: {}",
392             bp_path,
393             String::from_utf8_lossy(&bpfmt_output.stderr)
394         );
395     }
396 
397     if let Some(patch_path) = &package_cfg.patch {
398         let patch_output =
399             Command::new("patch").arg("-s").arg(&bp_path).arg(patch_path).output()?;
400         if !patch_output.status.success() {
401             eprintln!("WARNING: failed to apply patch {:?}", patch_path);
402         }
403         // Re-run bpfmt after the patch so
404         let bpfmt_output = Command::new("bpfmt").arg("-w").arg(&bp_path).output()?;
405         if !bpfmt_output.status.success() {
406             eprintln!(
407                 "WARNING: bpfmt -w {:?} failed after patch: {}",
408                 bp_path,
409                 String::from_utf8_lossy(&bpfmt_output.stderr)
410             );
411         }
412     }
413 
414     Ok(())
415 }
416 
417 /// Convert a `Crate` into `BpModule`s.
418 ///
419 /// If messy business logic is necessary, prefer putting it here.
crate_to_bp_modules( crate_: &Crate, cfg: &Config, package_cfg: &PackageConfig, extra_srcs: &[String], ) -> Result<Vec<BpModule>>420 fn crate_to_bp_modules(
421     crate_: &Crate,
422     cfg: &Config,
423     package_cfg: &PackageConfig,
424     extra_srcs: &[String],
425 ) -> Result<Vec<BpModule>> {
426     let mut modules = Vec::new();
427     for crate_type in &crate_.types {
428         let host = if package_cfg.device_supported.unwrap_or(true) { "" } else { "_host" };
429         let rlib = if package_cfg.force_rlib { "_rlib" } else { "" };
430         let (module_type, module_name, stem) = match crate_type {
431             CrateType::Bin => {
432                 ("rust_binary".to_string() + host, crate_.name.clone(), crate_.name.clone())
433             }
434             CrateType::Lib | CrateType::RLib => {
435                 let stem = "lib".to_string() + &crate_.name;
436                 ("rust_library".to_string() + rlib + host, stem.clone(), stem)
437             }
438             CrateType::DyLib => {
439                 let stem = "lib".to_string() + &crate_.name;
440                 ("rust_library".to_string() + host + "_dylib", stem.clone() + "_dylib", stem)
441             }
442             CrateType::CDyLib => {
443                 let stem = "lib".to_string() + &crate_.name;
444                 ("rust_ffi".to_string() + host + "_shared", stem.clone() + "_shared", stem)
445             }
446             CrateType::StaticLib => {
447                 let stem = "lib".to_string() + &crate_.name;
448                 ("rust_ffi".to_string() + host + "_static", stem.clone() + "_static", stem)
449             }
450             CrateType::ProcMacro => {
451                 let stem = "lib".to_string() + &crate_.name;
452                 ("rust_proc_macro".to_string(), stem.clone(), stem)
453             }
454             CrateType::Test | CrateType::TestNoHarness => {
455                 let suffix = crate_.main_src.to_string_lossy().into_owned();
456                 let suffix = suffix.replace('/', "_").replace(".rs", "");
457                 let stem = crate_.package_name.clone() + "_test_" + &suffix;
458                 if crate_type == &CrateType::TestNoHarness {
459                     eprintln!(
460                         "WARNING: ignoring test \"{}\" with harness=false. not supported yet",
461                         stem
462                     );
463                     return Ok(Vec::new());
464                 }
465                 ("rust_test".to_string() + host, stem.clone(), stem)
466             }
467         };
468 
469         let mut m = BpModule::new(module_type.clone());
470         let module_name = cfg.module_name_overrides.get(&module_name).unwrap_or(&module_name);
471         if cfg.module_blocklist.contains(module_name) {
472             continue;
473         }
474         m.props.set("name", module_name.clone());
475         if &stem != module_name {
476             m.props.set("stem", stem);
477         }
478 
479         if let Some(defaults) = &cfg.global_defaults {
480             m.props.set("defaults", vec![defaults.clone()]);
481         }
482 
483         if package_cfg.host_supported.unwrap_or(true)
484             && package_cfg.device_supported.unwrap_or(true)
485             && module_type != "rust_proc_macro"
486         {
487             m.props.set("host_supported", true);
488         }
489 
490         m.props.set("crate_name", crate_.name.clone());
491         m.props.set("cargo_env_compat", true);
492 
493         if let Some(version) = &crate_.version {
494             m.props.set("cargo_pkg_version", version.clone());
495         }
496 
497         if crate_.types.contains(&CrateType::Test) {
498             m.props.set("test_suites", vec!["general-tests"]);
499             m.props.set("auto_gen_config", true);
500             if package_cfg.host_supported.unwrap_or(true) {
501                 m.props.object("test_options").set("unit_test", !package_cfg.no_presubmit);
502             }
503         }
504 
505         let mut srcs = vec![crate_.main_src.to_string_lossy().to_string()];
506         srcs.extend(extra_srcs.iter().cloned());
507         m.props.set("srcs", srcs);
508 
509         m.props.set("edition", crate_.edition.clone());
510         if !crate_.features.is_empty() {
511             m.props.set("features", crate_.features.clone());
512         }
513         if !crate_.cfgs.is_empty() {
514             m.props.set("cfgs", crate_.cfgs.clone());
515         }
516 
517         let mut flags = Vec::new();
518         if !crate_.cap_lints.is_empty() {
519             flags.push(crate_.cap_lints.clone());
520         }
521         flags.extend(crate_.codegens.clone());
522         if !flags.is_empty() {
523             m.props.set("flags", flags);
524         }
525 
526         let mut rust_libs = Vec::new();
527         let mut proc_macro_libs = Vec::new();
528         for (extern_name, filename) in &crate_.externs {
529             if extern_name == "proc_macro" {
530                 continue;
531             }
532             let filename =
533                 filename.as_ref().unwrap_or_else(|| panic!("no filename for {}", extern_name));
534             // Example filename: "libgetrandom-fd8800939535fc59.rmeta"
535             static REGEX: Lazy<Regex> =
536                 Lazy::new(|| Regex::new(r"^lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$").unwrap());
537             let lib_name = if let Some(x) = REGEX.captures(filename).and_then(|x| x.get(1)) {
538                 x
539             } else {
540                 bail!("bad filename for extern {}: {}", extern_name, filename);
541             };
542             if filename.ends_with(".rlib") || filename.ends_with(".rmeta") {
543                 rust_libs.push(lib_name.as_str().to_string());
544             } else if filename.ends_with(".so") {
545                 // Assume .so files are always proc_macros. May not always be right.
546                 proc_macro_libs.push(lib_name.as_str().to_string());
547             } else {
548                 unreachable!();
549             }
550         }
551 
552         // Add "lib" prefix and apply name overrides.
553         let process_lib_deps = |libs: Vec<String>| -> Vec<String> {
554             let mut result = Vec::new();
555             for x in libs {
556                 let module_name = "lib".to_string() + x.as_str();
557                 let module_name =
558                     cfg.module_name_overrides.get(&module_name).unwrap_or(&module_name);
559                 if package_cfg.dep_blocklist.contains(module_name) {
560                     continue;
561                 }
562                 result.push(module_name.to_string());
563             }
564             result.sort();
565             result
566         };
567         if !rust_libs.is_empty() {
568             m.props.set("rustlibs", process_lib_deps(rust_libs));
569         }
570         if !proc_macro_libs.is_empty() {
571             m.props.set("proc_macros", process_lib_deps(proc_macro_libs));
572         }
573         if !crate_.static_libs.is_empty() {
574             m.props.set("static_libs", process_lib_deps(crate_.static_libs.clone()));
575         }
576         if !crate_.shared_libs.is_empty() {
577             m.props.set("shared_libs", process_lib_deps(crate_.shared_libs.clone()));
578         }
579 
580         if !cfg.apex_available.is_empty()
581             && [
582                 CrateType::Lib,
583                 CrateType::RLib,
584                 CrateType::DyLib,
585                 CrateType::CDyLib,
586                 CrateType::StaticLib,
587             ]
588             .contains(crate_type)
589         {
590             m.props.set("apex_available", cfg.apex_available.clone());
591         }
592 
593         if let Some(visibility) = cfg.module_visibility.get(module_name) {
594             m.props.set("visibility", visibility.clone());
595         }
596 
597         if let Some(path) = &package_cfg.add_module_block {
598             let content = std::fs::read_to_string(path)
599                 .with_context(|| format!("failed to read {path:?}"))?;
600             m.props.raw_block = Some(content);
601         }
602 
603         modules.push(m);
604     }
605     Ok(modules)
606 }
607