• 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;
31 mod config;
32 
33 use crate::config::Config;
34 use crate::config::PackageConfig;
35 use crate::config::PackageVariantConfig;
36 use crate::config::VariantConfig;
37 use anyhow::anyhow;
38 use anyhow::bail;
39 use anyhow::Context;
40 use anyhow::Result;
41 use bp::*;
42 use cargo::{
43     cargo_out::parse_cargo_out, metadata::parse_cargo_metadata_str, Crate, CrateType, ExternType,
44 };
45 use clap::Parser;
46 use clap::Subcommand;
47 use log::debug;
48 use nix::fcntl::OFlag;
49 use nix::unistd::pipe2;
50 use std::collections::BTreeMap;
51 use std::collections::VecDeque;
52 use std::env;
53 use std::fs::{read_to_string, write, File};
54 use std::io::{Read, Write};
55 use std::path::Path;
56 use std::path::PathBuf;
57 use std::process::{Command, Stdio};
58 use std::sync::LazyLock;
59 use tempfile::tempdir;
60 
61 // Major TODOs
62 //  * handle errors, esp. in cargo.out parsing. they should fail the program with an error code
63 //  * handle warnings. put them in comments in the android.bp, some kind of report section
64 
65 /// Rust modules which shouldn't use the default generated names, to avoid conflicts or confusion.
66 pub static RENAME_MAP: LazyLock<BTreeMap<&str, &str>> = LazyLock::new(|| {
67     [
68         ("libash", "libash_rust"),
69         ("libatomic", "libatomic_rust"),
70         ("libbacktrace", "libbacktrace_rust"),
71         ("libbase", "libbase_rust"),
72         ("libbase64", "libbase64_rust"),
73         ("libfuse", "libfuse_rust"),
74         ("libgcc", "libgcc_rust"),
75         ("liblog", "liblog_rust"),
76         ("libminijail", "libminijail_rust"),
77         ("libsync", "libsync_rust"),
78         ("libx86_64", "libx86_64_rust"),
79         ("libxml", "libxml_rust"),
80         ("protoc_gen_rust", "protoc-gen-rust"),
81     ]
82     .into_iter()
83     .collect()
84 });
85 
86 /// This map tracks Rust crates that have special rules.mk modules that were not
87 /// generated automatically by this script. Examples include compiler builtins
88 /// and other foundational libraries. It also tracks the location of rules.mk
89 /// build files for crates that are not under external/rust/crates.
90 pub static RULESMK_RENAME_MAP: LazyLock<BTreeMap<&str, &str>> = LazyLock::new(|| {
91     [
92         ("liballoc", "trusty/user/base/lib/liballoc-rust"),
93         ("libcompiler_builtins", "trusty/user/base/lib/libcompiler_builtins-rust"),
94         ("libcore", "trusty/user/base/lib/libcore-rust"),
95         ("libhashbrown", "trusty/user/base/lib/libhashbrown-rust"),
96         ("libpanic_abort", "trusty/user/base/lib/libpanic_abort-rust"),
97         ("libstd", "trusty/user/base/lib/libstd-rust"),
98         ("libstd_detect", "trusty/user/base/lib/libstd_detect-rust"),
99         ("libunwind", "trusty/user/base/lib/libunwind-rust"),
100     ]
101     .into_iter()
102     .collect()
103 });
104 
105 /// Given a proposed module name, returns `None` if it is blocked by the given config, or
106 /// else apply any name overrides and returns the name to use.
override_module_name( module_name: &str, blocklist: &[String], module_name_overrides: &BTreeMap<String, String>, rename_map: &BTreeMap<&str, &str>, ) -> Option<String>107 fn override_module_name(
108     module_name: &str,
109     blocklist: &[String],
110     module_name_overrides: &BTreeMap<String, String>,
111     rename_map: &BTreeMap<&str, &str>,
112 ) -> Option<String> {
113     if blocklist.iter().any(|blocked_name| blocked_name == module_name) {
114         None
115     } else if let Some(overridden_name) = module_name_overrides.get(module_name) {
116         Some(overridden_name.to_string())
117     } else if let Some(renamed) = rename_map.get(module_name) {
118         Some(renamed.to_string())
119     } else {
120         Some(module_name.to_string())
121     }
122 }
123 
124 /// Command-line parameters for `cargo_embargo`.
125 #[derive(Parser, Debug)]
126 struct Args {
127     /// Use the cargo binary in the `cargo_bin` directory. Defaults to using the Android prebuilt.
128     #[clap(long)]
129     cargo_bin: Option<PathBuf>,
130     /// Store `cargo build` output in this directory. If not set, a temporary directory is created and used.
131     #[clap(long)]
132     cargo_out_dir: Option<PathBuf>,
133     /// Skip the `cargo build` commands and reuse the "cargo.out" file from a previous run if
134     /// available. Requires setting --cargo_out_dir.
135     #[clap(long)]
136     reuse_cargo_out: bool,
137     #[command(subcommand)]
138     mode: Mode,
139 }
140 
141 #[derive(Clone, Debug, Subcommand)]
142 enum Mode {
143     /// Generates `Android.bp` files for the crates under the current directory using the given
144     /// config file.
145     Generate {
146         /// `cargo_embargo.json` config file to use.
147         config: PathBuf,
148     },
149     /// Dumps information about the crates to the given JSON file.
150     DumpCrates {
151         /// `cargo_embargo.json` config file to use.
152         config: PathBuf,
153         /// Path to `crates.json` to output.
154         crates: PathBuf,
155     },
156     /// Tries to automatically generate a suitable `cargo_embargo.json` config file for the package
157     /// in the current directory.
158     Autoconfig {
159         /// `cargo_embargo.json` config file to create.
160         config: PathBuf,
161     },
162 }
163 
main() -> Result<()>164 fn main() -> Result<()> {
165     env_logger::init();
166     let args = Args::parse();
167 
168     if args.reuse_cargo_out && args.cargo_out_dir.is_none() {
169         return Err(anyhow!("Must specify --cargo_out_dir with --reuse_cargo_out"));
170     }
171     let tempdir = tempdir()?;
172     let intermediates_dir = args.cargo_out_dir.as_deref().unwrap_or(tempdir.path());
173 
174     match &args.mode {
175         Mode::DumpCrates { config, crates } => {
176             dump_crates(&args, config, crates, intermediates_dir)?;
177         }
178         Mode::Generate { config } => {
179             run_embargo(&args, config, intermediates_dir)?;
180         }
181         Mode::Autoconfig { config } => {
182             autoconfig(&args, config, intermediates_dir)?;
183         }
184     }
185 
186     Ok(())
187 }
188 
189 /// Runs cargo_embargo with the given JSON configuration string, but dumps the crate data to the
190 /// given `crates.json` file rather than generating an `Android.bp`.
dump_crates( args: &Args, config_filename: &Path, crates_filename: &Path, intermediates_dir: &Path, ) -> Result<()>191 fn dump_crates(
192     args: &Args,
193     config_filename: &Path,
194     crates_filename: &Path,
195     intermediates_dir: &Path,
196 ) -> Result<()> {
197     let cfg = Config::from_file(config_filename)?;
198     let crates = make_all_crates(args, &cfg, intermediates_dir)?;
199     serde_json::to_writer(
200         File::create(crates_filename)
201             .with_context(|| format!("Failed to create {:?}", crates_filename))?,
202         &crates,
203     )?;
204     Ok(())
205 }
206 
207 /// Tries to automatically generate a suitable `cargo_embargo.json` for the package in the current
208 /// directory.
autoconfig(args: &Args, config_filename: &Path, intermediates_dir: &Path) -> Result<()>209 fn autoconfig(args: &Args, config_filename: &Path, intermediates_dir: &Path) -> Result<()> {
210     println!("Trying default config with tests...");
211     let mut config_with_build = Config {
212         variants: vec![VariantConfig { tests: true, ..Default::default() }],
213         package: Default::default(),
214     };
215     let mut crates_with_build = make_all_crates(args, &config_with_build, intermediates_dir)?;
216 
217     let has_tests =
218         crates_with_build[0].iter().any(|c| c.types.contains(&CrateType::Test) && !c.empty_test);
219     if !has_tests {
220         println!("No tests, removing from config.");
221         config_with_build =
222             Config { variants: vec![Default::default()], package: Default::default() };
223         crates_with_build = make_all_crates(args, &config_with_build, intermediates_dir)?;
224     }
225 
226     println!("Trying without cargo build...");
227     let config_no_build = Config {
228         variants: vec![VariantConfig { run_cargo: false, tests: has_tests, ..Default::default() }],
229         package: Default::default(),
230     };
231     let crates_without_build = make_all_crates(args, &config_no_build, intermediates_dir)?;
232 
233     let config = if crates_with_build == crates_without_build {
234         println!("Output without build was the same, using that.");
235         config_no_build
236     } else {
237         println!("Output without build was different. Need to run cargo build.");
238         println!("With build: {}", serde_json::to_string_pretty(&crates_with_build)?);
239         println!("Without build: {}", serde_json::to_string_pretty(&crates_without_build)?);
240         config_with_build
241     };
242     write(config_filename, format!("{}\n", config.to_json_string()?))?;
243     println!(
244         "Wrote config to {0}. Run `cargo_embargo generate {0}` to use it.",
245         config_filename.to_string_lossy()
246     );
247 
248     Ok(())
249 }
250 
251 /// Finds the path to the directory containing the Android prebuilt Rust toolchain.
find_android_rust_toolchain() -> Result<PathBuf>252 fn find_android_rust_toolchain() -> Result<PathBuf> {
253     let platform_rustfmt = if cfg!(all(target_arch = "x86_64", target_os = "linux")) {
254         "linux-x86/stable/rustfmt"
255     } else if cfg!(all(target_arch = "x86_64", target_os = "macos")) {
256         "darwin-x86/stable/rustfmt"
257     } else if cfg!(all(target_arch = "x86_64", target_os = "windows")) {
258         "windows-x86/stable/rustfmt.exe"
259     } else {
260         bail!("No prebuilt Rust toolchain available for this platform.");
261     };
262 
263     let android_top = env::var("ANDROID_BUILD_TOP")
264         .context("ANDROID_BUILD_TOP was not set. Did you forget to run envsetup.sh?")?;
265     let stable_rustfmt = [android_top.as_str(), "prebuilts", "rust", platform_rustfmt]
266         .into_iter()
267         .collect::<PathBuf>();
268     let canonical_rustfmt = stable_rustfmt.canonicalize()?;
269     Ok(canonical_rustfmt.parent().unwrap().to_owned())
270 }
271 
272 /// Adds the given path to the start of the `PATH` environment variable.
add_to_path(extra_path: PathBuf) -> Result<()>273 fn add_to_path(extra_path: PathBuf) -> Result<()> {
274     let path = env::var_os("PATH").unwrap();
275     let mut paths = env::split_paths(&path).collect::<VecDeque<_>>();
276     paths.push_front(extra_path);
277     let new_path = env::join_paths(paths)?;
278     debug!("Set PATH to {:?}", new_path);
279     std::env::set_var("PATH", new_path);
280     Ok(())
281 }
282 
283 /// Calls make_crates for each variant in the given config.
make_all_crates(args: &Args, cfg: &Config, intermediates_dir: &Path) -> Result<Vec<Vec<Crate>>>284 fn make_all_crates(args: &Args, cfg: &Config, intermediates_dir: &Path) -> Result<Vec<Vec<Crate>>> {
285     cfg.variants.iter().map(|variant| make_crates(args, variant, intermediates_dir)).collect()
286 }
287 
make_crates(args: &Args, cfg: &VariantConfig, intermediates_dir: &Path) -> Result<Vec<Crate>>288 fn make_crates(args: &Args, cfg: &VariantConfig, intermediates_dir: &Path) -> Result<Vec<Crate>> {
289     if !Path::new("Cargo.toml").try_exists().context("when checking Cargo.toml")? {
290         bail!("Cargo.toml missing. Run in a directory with a Cargo.toml file.");
291     }
292 
293     // Add the custom cargo to PATH.
294     // NOTE: If the directory with cargo has more binaries, this could have some unpredictable side
295     // effects. That is partly intended though, because we want to use that cargo binary's
296     // associated rustc.
297     let cargo_bin = if let Some(cargo_bin) = &args.cargo_bin {
298         cargo_bin.to_owned()
299     } else {
300         // Find the Android prebuilt.
301         find_android_rust_toolchain()?
302     };
303     add_to_path(cargo_bin)?;
304 
305     let cargo_out_path = intermediates_dir.join("cargo.out");
306     let cargo_metadata_path = intermediates_dir.join("cargo.metadata");
307     let cargo_output = if args.reuse_cargo_out && cargo_out_path.exists() {
308         CargoOutput {
309             cargo_out: read_to_string(cargo_out_path)?,
310             cargo_metadata: read_to_string(cargo_metadata_path)?,
311         }
312     } else {
313         let cargo_output =
314             generate_cargo_out(cfg, intermediates_dir).context("generate_cargo_out failed")?;
315         if cfg.run_cargo {
316             write(cargo_out_path, &cargo_output.cargo_out)?;
317         }
318         write(cargo_metadata_path, &cargo_output.cargo_metadata)?;
319         cargo_output
320     };
321 
322     if cfg.run_cargo {
323         parse_cargo_out(&cargo_output).context("parse_cargo_out failed")
324     } else {
325         parse_cargo_metadata_str(&cargo_output.cargo_metadata, cfg)
326     }
327 }
328 
329 /// Runs cargo_embargo with the given JSON configuration file.
run_embargo(args: &Args, config_filename: &Path, intermediates_dir: &Path) -> Result<()>330 fn run_embargo(args: &Args, config_filename: &Path, intermediates_dir: &Path) -> Result<()> {
331     let intermediates_glob = intermediates_dir
332         .to_str()
333         .ok_or(anyhow!("Failed to convert intermediate dir path to string"))?
334         .to_string()
335         + "/target.tmp/**/build/*/out/*";
336 
337     let cfg = Config::from_file(config_filename)?;
338     let crates = make_all_crates(args, &cfg, intermediates_dir)?;
339 
340     // TODO: Use different directories for different variants.
341     // Find out files.
342     // Example: target.tmp/x86_64-unknown-linux-gnu/debug/build/metrics-d2dd799cebf1888d/out/event_details.rs
343     let num_variants = cfg.variants.len();
344     let mut package_out_files: BTreeMap<String, Vec<Vec<PathBuf>>> = BTreeMap::new();
345     for (variant_index, variant_cfg) in cfg.variants.iter().enumerate() {
346         if variant_cfg.package.iter().any(|(_, v)| v.copy_out) {
347             for entry in glob::glob(&intermediates_glob)? {
348                 match entry {
349                     Ok(path) => {
350                         let package_name = || -> Option<_> {
351                             let dir_name = path.parent()?.parent()?.file_name()?.to_str()?;
352                             Some(dir_name.rsplit_once('-')?.0)
353                         }()
354                         .unwrap_or_else(|| panic!("failed to parse out file path: {:?}", path));
355                         package_out_files
356                             .entry(package_name.to_string())
357                             .or_insert_with(|| vec![vec![]; num_variants])[variant_index]
358                             .push(path.clone());
359                     }
360                     Err(e) => eprintln!("failed to check for out files: {}", e),
361                 }
362             }
363         }
364     }
365 
366     // If we were configured to run cargo, check whether we could have got away without it.
367     if cfg.variants.iter().any(|variant| variant.run_cargo) && package_out_files.is_empty() {
368         let mut cfg_no_cargo = cfg.clone();
369         for variant in &mut cfg_no_cargo.variants {
370             variant.run_cargo = false;
371         }
372         let crates_no_cargo = make_all_crates(args, &cfg_no_cargo, intermediates_dir)?;
373         if crates_no_cargo == crates {
374             eprintln!("Running cargo appears to be unnecessary for this crate, consider adding `\"run_cargo\": false` to your cargo_embargo.json.");
375         }
376     }
377 
378     write_all_build_files(&cfg, crates, &package_out_files)
379 }
380 
381 /// Input is indexed by variant, then all crates for that variant.
382 /// Output is a map from package directory to a list of variants, with all crates for that package
383 /// and variant.
group_by_package(crates: Vec<Vec<Crate>>) -> BTreeMap<PathBuf, Vec<Vec<Crate>>>384 fn group_by_package(crates: Vec<Vec<Crate>>) -> BTreeMap<PathBuf, Vec<Vec<Crate>>> {
385     let mut module_by_package: BTreeMap<PathBuf, Vec<Vec<Crate>>> = BTreeMap::new();
386 
387     let num_variants = crates.len();
388     for (i, variant_crates) in crates.into_iter().enumerate() {
389         for c in variant_crates {
390             let package_variants = module_by_package
391                 .entry(c.package_dir.clone())
392                 .or_insert_with(|| vec![vec![]; num_variants]);
393             package_variants[i].push(c);
394         }
395     }
396     module_by_package
397 }
398 
write_all_build_files( cfg: &Config, crates: Vec<Vec<Crate>>, package_out_files: &BTreeMap<String, Vec<Vec<PathBuf>>>, ) -> Result<()>399 fn write_all_build_files(
400     cfg: &Config,
401     crates: Vec<Vec<Crate>>,
402     package_out_files: &BTreeMap<String, Vec<Vec<PathBuf>>>,
403 ) -> Result<()> {
404     // Group by package.
405     let module_by_package = group_by_package(crates);
406 
407     let num_variants = cfg.variants.len();
408     let empty_package_out_files = vec![vec![]; num_variants];
409     let mut has_error = false;
410     // Write a build file per package.
411     for (package_dir, crates) in module_by_package {
412         let package_name = &crates.iter().flatten().next().unwrap().package_name;
413         if let Err(e) = write_build_files(
414             cfg,
415             package_name,
416             package_dir,
417             &crates,
418             package_out_files.get(package_name).unwrap_or(&empty_package_out_files),
419         ) {
420             // print the error, but continue to accumulate all of the errors
421             eprintln!("ERROR: {:#}", e);
422             has_error = true;
423         }
424     }
425     if has_error {
426         panic!("Encountered fatal errors that must be fixed.");
427     }
428 
429     Ok(())
430 }
431 
432 /// Runs the given command, and returns its standard output and (optionally) standard error as a string.
run_cargo(cmd: &mut Command, include_stderr: bool) -> Result<String>433 fn run_cargo(cmd: &mut Command, include_stderr: bool) -> Result<String> {
434     let (pipe_read, pipe_write) = pipe2(OFlag::O_CLOEXEC)?;
435     if include_stderr {
436         cmd.stderr(pipe_write.try_clone()?);
437     }
438     cmd.stdout(pipe_write).stdin(Stdio::null());
439     debug!("Running: {:?}\n", cmd);
440     let mut child = cmd.spawn()?;
441 
442     // Unset the stdout and stderr for the command so that they are dropped in this process.
443     // Otherwise the `read_to_string` below will block forever as there is still an open write file
444     // descriptor for the pipe even after the child finishes.
445     cmd.stderr(Stdio::null()).stdout(Stdio::null());
446 
447     let mut output = String::new();
448     File::from(pipe_read).read_to_string(&mut output)?;
449     let status = child.wait()?;
450     if !status.success() {
451         bail!(
452             "cargo command `{:?}` failed with exit status: {:?}.\nOutput: \n------\n{}\n------",
453             cmd,
454             status,
455             output
456         );
457     }
458 
459     Ok(output)
460 }
461 
462 /// The raw output from running `cargo metadata`, `cargo build` and other commands.
463 #[derive(Clone, Debug, Eq, PartialEq)]
464 pub struct CargoOutput {
465     cargo_metadata: String,
466     cargo_out: String,
467 }
468 
469 /// Run various cargo commands and returns the output.
generate_cargo_out(cfg: &VariantConfig, intermediates_dir: &Path) -> Result<CargoOutput>470 fn generate_cargo_out(cfg: &VariantConfig, intermediates_dir: &Path) -> Result<CargoOutput> {
471     let verbose_args = ["-v"];
472     let target_dir = intermediates_dir.join("target.tmp");
473 
474     // cargo clean
475     run_cargo(Command::new("cargo").arg("clean").arg("--target-dir").arg(&target_dir), true)
476         .context("Running cargo clean")?;
477 
478     let default_target = "x86_64-unknown-linux-gnu";
479     let feature_args = if let Some(features) = &cfg.features {
480         if features.is_empty() {
481             vec!["--no-default-features".to_string()]
482         } else {
483             vec!["--no-default-features".to_string(), "--features".to_string(), features.join(",")]
484         }
485     } else {
486         vec![]
487     };
488 
489     let workspace_args = if cfg.workspace {
490         let mut v = vec!["--workspace".to_string()];
491         if !cfg.workspace_excludes.is_empty() {
492             for x in cfg.workspace_excludes.iter() {
493                 v.push("--exclude".to_string());
494                 v.push(x.clone());
495             }
496         }
497         v
498     } else {
499         vec![]
500     };
501 
502     // cargo metadata
503     let cargo_metadata = run_cargo(
504         Command::new("cargo")
505             .arg("metadata")
506             .arg("-q") // don't output warnings to stderr
507             .arg("--format-version")
508             .arg("1")
509             .args(&feature_args),
510         false,
511     )
512     .context("Running cargo metadata")?;
513 
514     let mut cargo_out = String::new();
515     if cfg.run_cargo {
516         let mut rustflags = vec!["--cap-lints".to_string(), "allow".to_string()];
517         if !cfg.extra_cfg.is_empty() {
518             rustflags.extend(cfg.extra_cfg.iter().map(|cfg_flag| format!("--cfg {}", cfg_flag)));
519         }
520         let envs = vec![("RUSTFLAGS", rustflags.join(" "))];
521 
522         // cargo build
523         cargo_out += &run_cargo(
524             Command::new("cargo")
525                 .envs(envs.clone())
526                 .args(["build", "--target", default_target])
527                 .args(verbose_args)
528                 .arg("--target-dir")
529                 .arg(&target_dir)
530                 .args(&workspace_args)
531                 .args(&feature_args),
532             true,
533         )?;
534 
535         if cfg.tests {
536             // cargo build --tests
537             cargo_out += &run_cargo(
538                 Command::new("cargo")
539                     .envs(envs.clone())
540                     .args(["build", "--target", default_target, "--tests"])
541                     .args(verbose_args)
542                     .arg("--target-dir")
543                     .arg(&target_dir)
544                     .args(&workspace_args)
545                     .args(&feature_args),
546                 true,
547             )?;
548             // cargo test -- --list
549             cargo_out += &run_cargo(
550                 Command::new("cargo")
551                     .envs(envs)
552                     .args(["test", "--target", default_target])
553                     .arg("--target-dir")
554                     .arg(&target_dir)
555                     .args(&workspace_args)
556                     .args(&feature_args)
557                     .args(["--", "--list"]),
558                 true,
559             )?;
560         }
561     }
562 
563     Ok(CargoOutput { cargo_metadata, cargo_out })
564 }
565 
566 /// Read and return license and other header lines from a build file.
567 ///
568 /// Skips initial comment lines, then returns all lines before the first line
569 /// starting with `rust_`, `genrule {`, or `LOCAL_DIR`.
570 ///
571 /// If `path` could not be read and a license is required, return a
572 /// placeholder license TODO line.
read_license_header(path: &Path, require_license: bool) -> Result<String>573 fn read_license_header(path: &Path, require_license: bool) -> Result<String> {
574     // Keep the old license header.
575     match std::fs::read_to_string(path) {
576         Ok(s) => Ok(s
577             .lines()
578             .skip_while(|l| l.starts_with("//") || l.starts_with('#'))
579             .take_while(|l| {
580                 !l.starts_with("rust_")
581                     && !l.starts_with("genrule {")
582                     && !l.starts_with("LOCAL_DIR")
583             })
584             .collect::<Vec<&str>>()
585             .join("\n")),
586         Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
587             let placeholder = if require_license {
588                 "// DO NOT SUBMIT: Add license before submitting.\n"
589             } else {
590                 ""
591             };
592             Ok(placeholder.to_string())
593         }
594         Err(e) => Err(anyhow!("error when reading {path:?}: {e}")),
595     }
596 }
597 
598 /// Create the build file for `package_dir`.
599 ///
600 /// `crates` and `out_files` are both indexed by variant.
write_build_files( cfg: &Config, package_name: &str, package_dir: PathBuf, crates: &[Vec<Crate>], out_files: &[Vec<PathBuf>], ) -> Result<()>601 fn write_build_files(
602     cfg: &Config,
603     package_name: &str,
604     package_dir: PathBuf,
605     crates: &[Vec<Crate>],
606     out_files: &[Vec<PathBuf>],
607 ) -> Result<()> {
608     assert_eq!(crates.len(), out_files.len());
609 
610     let mut bp_contents = String::new();
611     let mut mk_contents = String::new();
612     for (variant_index, variant_config) in cfg.variants.iter().enumerate() {
613         let variant_crates = &crates[variant_index];
614         let def = PackageVariantConfig::default();
615         let package_variant_cfg = variant_config.package.get(package_name).unwrap_or(&def);
616 
617         // If `copy_out` is enabled and there are any generated out files for the package, copy them to
618         // the appropriate directory.
619         if package_variant_cfg.copy_out && !out_files[variant_index].is_empty() {
620             let out_dir = package_dir.join("out");
621             if !out_dir.exists() {
622                 std::fs::create_dir(&out_dir).expect("failed to create out dir");
623             }
624 
625             for f in out_files[variant_index].iter() {
626                 let dest = out_dir.join(f.file_name().unwrap());
627                 std::fs::copy(f, dest).expect("failed to copy out file");
628             }
629         }
630 
631         if variant_config.generate_androidbp {
632             bp_contents += &generate_android_bp(
633                 variant_config,
634                 package_variant_cfg,
635                 package_name,
636                 variant_crates,
637                 &out_files[variant_index],
638             )?;
639         }
640         if variant_config.generate_rulesmk {
641             mk_contents += &generate_rules_mk(
642                 variant_config,
643                 package_variant_cfg,
644                 package_name,
645                 variant_crates,
646                 &out_files[variant_index],
647             )?;
648         }
649     }
650     let main_module_name_overrides = &cfg.variants.first().unwrap().module_name_overrides;
651     if !mk_contents.is_empty() {
652         // If rules.mk is generated, then make it accessible via dirgroup.
653         bp_contents += &generate_android_bp_for_rules_mk(package_name, main_module_name_overrides)?;
654     }
655 
656     let def = PackageConfig::default();
657     let package_cfg = cfg.package.get(package_name).unwrap_or(&def);
658     if let Some(path) = &package_cfg.add_toplevel_block {
659         bp_contents +=
660             &std::fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
661         bp_contents += "\n";
662     }
663     if !bp_contents.is_empty() {
664         let output_path = package_dir.join("Android.bp");
665         let package_header = generate_android_bp_package_header(
666             package_name,
667             package_cfg,
668             read_license_header(&output_path, true)?.trim(),
669             crates,
670             main_module_name_overrides,
671         )?;
672         let bp_contents = package_header + &bp_contents;
673         write_format_android_bp(&output_path, &bp_contents, package_cfg.patch.as_deref())?;
674     }
675     if !mk_contents.is_empty() {
676         let output_path = package_dir.join("rules.mk");
677         let mk_contents = "# This file is generated by cargo_embargo.\n".to_owned()
678             + "# Do not modify this file after the LOCAL_DIR line\n"
679             + "# because the changes will be overridden on upgrade.\n"
680             + "# Content before the first line starting with LOCAL_DIR is preserved.\n"
681             + read_license_header(&output_path, false)?.trim()
682             + "\n"
683             + &mk_contents;
684         File::create(&output_path)?.write_all(mk_contents.as_bytes())?;
685         if let Some(patch) = package_cfg.rulesmk_patch.as_deref() {
686             apply_patch_file(&output_path, patch)?;
687         }
688     }
689 
690     Ok(())
691 }
692 
generate_android_bp_package_header( package_name: &str, package_cfg: &PackageConfig, license_header: &str, crates: &[Vec<Crate>], module_name_overrides: &BTreeMap<String, String>, ) -> Result<String>693 fn generate_android_bp_package_header(
694     package_name: &str,
695     package_cfg: &PackageConfig,
696     license_header: &str,
697     crates: &[Vec<Crate>],
698     module_name_overrides: &BTreeMap<String, String>,
699 ) -> Result<String> {
700     let crates = crates.iter().flatten().collect::<Vec<_>>();
701     if let Some(first) = crates.first() {
702         if let Some(license) = first.license.as_ref() {
703             if crates.iter().all(|c| c.license.as_ref() == Some(license)) {
704                 let mut modules = Vec::new();
705                 let licenses = choose_licenses(license)?;
706 
707                 let default_license_name = format!("external_rust_crates_{}_license", package_name);
708 
709                 let license_name = match override_module_name(
710                     &default_license_name,
711                     &[],
712                     module_name_overrides,
713                     &RENAME_MAP,
714                 ) {
715                     Some(x) => x,
716                     None => default_license_name,
717                 };
718 
719                 let mut package_module = BpModule::new("package".to_string());
720                 package_module.props.set("default_team", "trendy_team_android_rust");
721                 package_module.props.set("default_applicable_licenses", vec![license_name.clone()]);
722                 modules.push(package_module);
723 
724                 let mut license_module = BpModule::new("license".to_string());
725                 license_module.props.set("name", license_name);
726                 license_module.props.set("visibility", vec![":__subpackages__"]);
727                 license_module.props.set(
728                     "license_kinds",
729                     licenses
730                         .into_iter()
731                         .map(|license| format!("SPDX-license-identifier-{}", license))
732                         .collect::<Vec<_>>(),
733                 );
734                 let license_text = package_cfg.license_text.clone().unwrap_or_else(|| {
735                     vec![first.license_file.as_deref().unwrap_or("LICENSE").to_string()]
736                 });
737                 license_module.props.set("license_text", license_text);
738                 modules.push(license_module);
739 
740                 let mut bp_contents = "// This file is generated by cargo_embargo.\n".to_owned()
741                 + "// Do not modify this file because the changes will be overridden on upgrade.\n\n";
742                 for m in modules {
743                     m.write(&mut bp_contents)?;
744                     bp_contents += "\n";
745                 }
746                 return Ok(bp_contents);
747             } else {
748                 eprintln!("Crates have different licenses.");
749             }
750         }
751     }
752 
753     Ok("// This file is generated by cargo_embargo.\n".to_owned()
754         + "// Do not modify this file after the first \"rust_*\" or \"genrule\" module\n"
755         + "// because the changes will be overridden on upgrade.\n"
756         + "// Content before the first \"rust_*\" or \"genrule\" module is preserved.\n\n"
757         + license_header
758         + "\n")
759 }
760 
761 /// Given an SPDX license expression that may offer a choice between several licenses, choose one or
762 /// more to use.
choose_licenses(license: &str) -> Result<Vec<&str>>763 fn choose_licenses(license: &str) -> Result<Vec<&str>> {
764     Ok(match license {
765         // Variations on "MIT OR Apache-2.0"
766         "MIT OR Apache-2.0" => vec!["Apache-2.0"],
767         "Apache-2.0 OR MIT" => vec!["Apache-2.0"],
768         "MIT/Apache-2.0" => vec!["Apache-2.0"],
769         "Apache-2.0/MIT" => vec!["Apache-2.0"],
770         "Apache-2.0 / MIT" => vec!["Apache-2.0"],
771 
772         // Variations on "BSD-* OR Apache-2.0"
773         "Apache-2.0 OR BSD-3-Clause" => vec!["Apache-2.0"],
774         "Apache-2.0 or BSD-3-Clause" => vec!["Apache-2.0"],
775         "BSD-3-Clause OR Apache-2.0" => vec!["Apache-2.0"],
776 
777         // Variations on "BSD-* OR MIT OR Apache-2.0"
778         "BSD-3-Clause OR MIT OR Apache-2.0" => vec!["Apache-2.0"],
779         "BSD-2-Clause OR Apache-2.0 OR MIT" => vec!["Apache-2.0"],
780 
781         // Variations on "Zlib OR MIT OR Apache-2.0"
782         "Zlib OR Apache-2.0 OR MIT" => vec!["Apache-2.0"],
783         "MIT OR Apache-2.0 OR Zlib" => vec!["Apache-2.0"],
784 
785         // Variations on "Apache-2.0 OR *"
786         "Apache-2.0 OR BSL-1.0" => vec!["Apache-2.0"],
787         "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" => vec!["Apache-2.0"],
788 
789         // Variations on "Unlicense OR MIT"
790         "Unlicense OR MIT" => vec!["MIT"],
791         "Unlicense/MIT" => vec!["MIT"],
792 
793         // Multiple licenses.
794         "(MIT OR Apache-2.0) AND Unicode-DFS-2016" => vec!["Apache-2.0", "Unicode-DFS-2016"],
795         "MIT AND BSD-3-Clause" => vec!["BSD-3-Clause", "MIT"],
796         // Usually we interpret "/" as "OR", but in the case of libfuzzer-sys, closer
797         // inspection of the terms indicates the correct interpretation is "(MIT OR APACHE) AND NCSA".
798         "MIT/Apache-2.0/NCSA" => vec!["Apache-2.0", "NCSA"],
799 
800         // Variations on "Apache-2.0 AND BSD-*"
801         "Apache-2.0 AND BSD-3-Clause" => vec!["Apache-2.0", "BSD-3-Clause"],
802 
803         // Other cases.
804         "MIT OR LGPL-3.0-or-later" => vec!["MIT"],
805         "MIT/BSD-3-Clause" => vec!["MIT"],
806         "MIT AND (MIT OR Apache-2.0)" => vec!["MIT"],
807         "0BSD OR MIT OR Apache-2.0" => vec!["Apache-2.0"],
808 
809         "LGPL-2.1-only OR BSD-2-Clause" => vec!["BSD-2-Clause"],
810         _ => {
811             // If there is whitespace, it is probably an SPDX expression.
812             if license.contains(char::is_whitespace) {
813                 bail!("Unrecognized license: {license}");
814             }
815             vec![license]
816         }
817     })
818 }
819 
820 /// Generates and returns a Soong Blueprint for the given set of crates, for a single variant of a
821 /// package.
generate_android_bp( cfg: &VariantConfig, package_cfg: &PackageVariantConfig, package_name: &str, crates: &[Crate], out_files: &[PathBuf], ) -> Result<String>822 fn generate_android_bp(
823     cfg: &VariantConfig,
824     package_cfg: &PackageVariantConfig,
825     package_name: &str,
826     crates: &[Crate],
827     out_files: &[PathBuf],
828 ) -> Result<String> {
829     let mut bp_contents = String::new();
830 
831     let mut modules = Vec::new();
832 
833     let extra_srcs = if package_cfg.copy_out && !out_files.is_empty() {
834         let outs: Vec<String> = out_files
835             .iter()
836             .map(|f| f.file_name().unwrap().to_str().unwrap().to_string())
837             .collect();
838 
839         let mut m = BpModule::new("genrule".to_string());
840         if let Some(module_name) = override_module_name(
841             &format!("copy_{}_build_out", package_name),
842             &cfg.module_blocklist,
843             &cfg.module_name_overrides,
844             &RENAME_MAP,
845         ) {
846             m.props.set("name", module_name.clone());
847             m.props.set("srcs", vec!["out/*"]);
848             m.props.set("cmd", "cp $(in) $(genDir)");
849             m.props.set("out", outs);
850             modules.push(m);
851 
852             vec![":".to_string() + &module_name]
853         } else {
854             vec![]
855         }
856     } else {
857         vec![]
858     };
859 
860     for c in crates {
861         modules.extend(crate_to_bp_modules(c, cfg, package_cfg, &extra_srcs).with_context(
862             || {
863                 format!(
864                     "failed to generate bp module for crate \"{}\" with package name \"{}\"",
865                     c.name, c.package_name
866                 )
867             },
868         )?);
869     }
870 
871     // In some cases there are nearly identical rustc invocations that that get processed into
872     // identical BP modules. So far, dedup'ing them is a good enough fix. At some point we might
873     // need something more complex, maybe like cargo2android's logic for merging crates.
874     modules.sort();
875     modules.dedup();
876 
877     modules.sort_by_key(|m| m.props.get_string("name").unwrap().to_string());
878     for m in modules {
879         m.write(&mut bp_contents)?;
880         bp_contents += "\n";
881     }
882     Ok(bp_contents)
883 }
884 
885 /// Generates and returns a Trusty rules.mk file for the given set of crates.
generate_rules_mk( cfg: &VariantConfig, package_cfg: &PackageVariantConfig, package_name: &str, crates: &[Crate], out_files: &[PathBuf], ) -> Result<String>886 fn generate_rules_mk(
887     cfg: &VariantConfig,
888     package_cfg: &PackageVariantConfig,
889     package_name: &str,
890     crates: &[Crate],
891     out_files: &[PathBuf],
892 ) -> Result<String> {
893     let out_files = if package_cfg.copy_out && !out_files.is_empty() {
894         out_files.iter().map(|f| f.file_name().unwrap().to_str().unwrap().to_string()).collect()
895     } else {
896         vec![]
897     };
898 
899     let crates: Vec<_> = crates
900         .iter()
901         .filter(|c| {
902             if c.types.contains(&CrateType::Bin) {
903                 eprintln!("WARNING: skipped generation of rules.mk for binary crate: {}", c.name);
904                 false
905             } else if c.types.iter().any(|t| t.is_test()) {
906                 // Test build file generation is not yet implemented
907                 eprintln!("WARNING: skipped generation of rules.mk for test crate: {}", c.name);
908                 false
909             } else {
910                 true
911             }
912         })
913         .collect();
914     let [crate_] = &crates[..] else {
915         bail!(
916             "Expected exactly one library crate for package {package_name} when generating \
917                rules.mk, found: {crates:?}"
918         );
919     };
920     crate_to_rulesmk(crate_, cfg, package_cfg, &out_files).with_context(|| {
921         format!(
922             "failed to generate rules.mk for crate \"{}\" with package name \"{}\"",
923             crate_.name, crate_.package_name
924         )
925     })
926 }
927 
928 /// Generates and returns a Soong Blueprint for a Trusty rules.mk
generate_android_bp_for_rules_mk( package_name: &str, module_name_overrides: &BTreeMap<String, String>, ) -> Result<String>929 fn generate_android_bp_for_rules_mk(
930     package_name: &str,
931     module_name_overrides: &BTreeMap<String, String>,
932 ) -> Result<String> {
933     let mut bp_contents = String::new();
934 
935     let mut m = BpModule::new("dirgroup".to_string());
936 
937     let default_dirgroup_name = format!("trusty_dirgroup_external_rust_crates_{}", package_name);
938     let dirgroup_name =
939         override_module_name(&default_dirgroup_name, &[], module_name_overrides, &RENAME_MAP)
940             .unwrap_or(default_dirgroup_name);
941     m.props.set("name", dirgroup_name);
942     m.props.set("dirs", vec!["."]);
943     m.props.set("visibility", vec!["//trusty/vendor/google/aosp/scripts"]);
944 
945     m.write(&mut bp_contents)?;
946     bp_contents += "\n";
947 
948     Ok(bp_contents)
949 }
950 
951 /// Apply patch from `patch_path` to file `output_path`.
952 ///
953 /// Warns but still returns ok if the patch did not cleanly apply,
apply_patch_file(output_path: &Path, patch_path: &Path) -> Result<()>954 fn apply_patch_file(output_path: &Path, patch_path: &Path) -> Result<()> {
955     let patch_output = Command::new("patch")
956         .arg("-s")
957         .arg("--no-backup-if-mismatch")
958         .arg(output_path)
959         .arg(patch_path)
960         .output()
961         .context("Running patch")?;
962     if !patch_output.status.success() {
963         let stdout = String::from_utf8(patch_output.stdout)?;
964         let stderr = String::from_utf8(patch_output.stderr)?;
965         // These errors will cause the cargo_embargo command to fail, but not yet!
966         bail!("failed to apply patch {patch_path:?}:\n\nout:\n{stdout}\n\nerr:\n{stderr}");
967     }
968     Ok(())
969 }
970 
971 /// Writes the given contents to the given `Android.bp` file, formats it with `bpfmt`, and applies
972 /// the patch if there is one.
write_format_android_bp( bp_path: &Path, bp_contents: &str, patch_path: Option<&Path>, ) -> Result<()>973 fn write_format_android_bp(
974     bp_path: &Path,
975     bp_contents: &str,
976     patch_path: Option<&Path>,
977 ) -> Result<()> {
978     File::create(bp_path)?.write_all(bp_contents.as_bytes())?;
979 
980     let bpfmt_output =
981         Command::new("bpfmt").arg("-w").arg(bp_path).output().context("Running bpfmt")?;
982     if !bpfmt_output.status.success() {
983         eprintln!(
984             "WARNING: bpfmt -w {:?} failed before patch: {}",
985             bp_path,
986             String::from_utf8_lossy(&bpfmt_output.stderr)
987         );
988     }
989 
990     if let Some(patch_path) = patch_path {
991         apply_patch_file(bp_path, patch_path)?;
992         // Re-run bpfmt after the patch so
993         let bpfmt_output = Command::new("bpfmt")
994             .arg("-w")
995             .arg(bp_path)
996             .output()
997             .context("Running bpfmt after patch")?;
998         if !bpfmt_output.status.success() {
999             eprintln!(
1000                 "WARNING: bpfmt -w {:?} failed after patch: {}",
1001                 bp_path,
1002                 String::from_utf8_lossy(&bpfmt_output.stderr)
1003             );
1004         }
1005     }
1006 
1007     Ok(())
1008 }
1009 
1010 /// Convert a `Crate` into `BpModule`s.
1011 ///
1012 /// If messy business logic is necessary, prefer putting it here.
crate_to_bp_modules( crate_: &Crate, cfg: &VariantConfig, package_cfg: &PackageVariantConfig, extra_srcs: &[String], ) -> Result<Vec<BpModule>>1013 fn crate_to_bp_modules(
1014     crate_: &Crate,
1015     cfg: &VariantConfig,
1016     package_cfg: &PackageVariantConfig,
1017     extra_srcs: &[String],
1018 ) -> Result<Vec<BpModule>> {
1019     let mut modules = Vec::new();
1020     for crate_type in &crate_.types {
1021         let host = if package_cfg.device_supported { "" } else { "_host" };
1022         let rlib = if package_cfg.force_rlib { "_rlib" } else { "" };
1023         let (module_type, module_name) = match crate_type {
1024             CrateType::Bin => ("rust_binary".to_string() + host, crate_.name.clone()),
1025             CrateType::Lib | CrateType::RLib => {
1026                 let stem = "lib".to_string() + &crate_.name;
1027                 ("rust_library".to_string() + host + rlib, stem)
1028             }
1029             CrateType::DyLib => {
1030                 let stem = "lib".to_string() + &crate_.name;
1031                 ("rust_library".to_string() + host + "_dylib", stem + "_dylib")
1032             }
1033             CrateType::CDyLib => {
1034                 let stem = "lib".to_string() + &crate_.name;
1035                 ("rust_ffi".to_string() + host + "_shared", stem + "_shared")
1036             }
1037             CrateType::StaticLib => {
1038                 let stem = "lib".to_string() + &crate_.name;
1039                 ("rust_ffi".to_string() + host + "_static", stem + "_static")
1040             }
1041             CrateType::ProcMacro => {
1042                 let stem = "lib".to_string() + &crate_.name;
1043                 ("rust_proc_macro".to_string(), stem)
1044             }
1045             CrateType::Test | CrateType::TestNoHarness => {
1046                 let suffix = crate_.main_src.to_string_lossy().into_owned();
1047                 let suffix = suffix.replace('/', "_").replace(".rs", "");
1048                 let stem = crate_.package_name.clone() + "_test_" + &suffix;
1049                 if crate_.empty_test {
1050                     return Ok(Vec::new());
1051                 }
1052                 if crate_type == &CrateType::TestNoHarness {
1053                     eprintln!(
1054                         "WARNING: ignoring test \"{}\" with harness=false. not supported yet",
1055                         stem
1056                     );
1057                     return Ok(Vec::new());
1058                 }
1059                 ("rust_test".to_string() + host, stem)
1060             }
1061         };
1062 
1063         let mut m = BpModule::new(module_type.clone());
1064         let Some(module_name) = override_module_name(
1065             &module_name,
1066             &cfg.module_blocklist,
1067             &cfg.module_name_overrides,
1068             &RENAME_MAP,
1069         ) else {
1070             continue;
1071         };
1072         if matches!(
1073             crate_type,
1074             CrateType::Lib
1075                 | CrateType::RLib
1076                 | CrateType::DyLib
1077                 | CrateType::CDyLib
1078                 | CrateType::StaticLib
1079         ) && !module_name.starts_with(&format!("lib{}", crate_.name))
1080         {
1081             bail!("Module name must start with lib{} but was {}", crate_.name, module_name);
1082         }
1083         m.props.set("name", module_name.clone());
1084 
1085         if let Some(defaults) = &cfg.global_defaults {
1086             m.props.set("defaults", vec![defaults.clone()]);
1087         }
1088 
1089         if package_cfg.host_supported
1090             && package_cfg.device_supported
1091             && module_type != "rust_proc_macro"
1092         {
1093             m.props.set("host_supported", true);
1094         }
1095 
1096         if module_type != "rust_proc_macro" {
1097             if package_cfg.host_supported && !package_cfg.host_cross_supported {
1098                 m.props.set("host_cross_supported", false);
1099             } else if crate_.externs.iter().any(|extern_dep| extern_dep.name == "proc_macro2") {
1100                 // proc_macro2 is host_cross_supported: false.
1101                 // If there's a dependency on it, then we shouldn't build for HostCross.
1102                 m.props.set("host_cross_supported", false);
1103             } else if crate_.package_name == "proc-macro2" {
1104                 m.props.set("host_cross_supported", false);
1105             }
1106         }
1107 
1108         if !crate_type.is_test() && package_cfg.host_supported && package_cfg.host_first_multilib {
1109             m.props.set("compile_multilib", "first");
1110         }
1111         if crate_type.is_library() {
1112             m.props.set_if_nonempty("include_dirs", package_cfg.exported_c_header_dir.clone());
1113         }
1114 
1115         m.props.set("crate_name", crate_.name.clone());
1116         m.props.set("cargo_env_compat", true);
1117 
1118         if let Some(version) = &crate_.version {
1119             m.props.set("cargo_pkg_version", version.clone());
1120         }
1121 
1122         if crate_.types.contains(&CrateType::Test) {
1123             m.props.set("test_suites", vec!["general-tests"]);
1124             m.props.set("auto_gen_config", true);
1125             if package_cfg.host_supported {
1126                 m.props.object("test_options").set("unit_test", !package_cfg.no_presubmit);
1127             }
1128         }
1129 
1130         m.props.set("crate_root", crate_.main_src.clone());
1131         m.props.set_if_nonempty("srcs", extra_srcs.to_owned());
1132 
1133         m.props.set("edition", crate_.edition.clone());
1134         m.props.set_if_nonempty("features", crate_.features.clone());
1135         m.props.set_if_nonempty(
1136             "cfgs",
1137             crate_
1138                 .cfgs
1139                 .clone()
1140                 .into_iter()
1141                 .filter(|crate_cfg| !cfg.cfg_blocklist.contains(crate_cfg))
1142                 .map(|crate_cfg| crate_cfg.replace(r#"""#, r#"\""#))
1143                 .collect(),
1144         );
1145 
1146         let flags = crate_.codegens.iter().map(|codegen| format!("-C {}", codegen)).collect();
1147         m.props.set_if_nonempty("flags", flags);
1148 
1149         let mut rust_libs = Vec::new();
1150         let mut proc_macro_libs = Vec::new();
1151         let mut aliases = Vec::new();
1152         for extern_dep in &crate_.externs {
1153             match extern_dep.extern_type {
1154                 ExternType::Rust => rust_libs.push(extern_dep.lib_name.clone()),
1155                 ExternType::ProcMacro => proc_macro_libs.push(extern_dep.lib_name.clone()),
1156             }
1157             if extern_dep.name != extern_dep.lib_name {
1158                 aliases.push(format!("{}:{}", extern_dep.lib_name, extern_dep.name));
1159             }
1160         }
1161 
1162         // Add "lib" prefix and apply name overrides.
1163         let process_lib_deps = |libs: Vec<String>| -> Vec<String> {
1164             let mut result = Vec::new();
1165             for x in libs {
1166                 let module_name = "lib".to_string() + x.as_str();
1167                 if let Some(module_name) = override_module_name(
1168                     &module_name,
1169                     &package_cfg.dep_blocklist,
1170                     &cfg.module_name_overrides,
1171                     &RENAME_MAP,
1172                 ) {
1173                     result.push(module_name);
1174                 }
1175             }
1176             result.sort();
1177             result.dedup();
1178             result
1179         };
1180         m.props.set_if_nonempty("rustlibs", process_lib_deps(rust_libs));
1181         m.props.set_if_nonempty("proc_macros", process_lib_deps(proc_macro_libs));
1182         let (whole_static_libs, static_libs) = process_lib_deps(crate_.static_libs.clone())
1183             .into_iter()
1184             .partition(|static_lib| package_cfg.whole_static_libs.contains(static_lib));
1185         m.props.set_if_nonempty("static_libs", static_libs);
1186         m.props.set_if_nonempty("whole_static_libs", whole_static_libs);
1187         m.props.set_if_nonempty("shared_libs", process_lib_deps(crate_.shared_libs.clone()));
1188         m.props.set_if_nonempty("aliases", aliases);
1189 
1190         if package_cfg.device_supported && !crate_type.is_test() {
1191             if cfg.native_bridge_supported {
1192                 m.props.set("native_bridge_supported", true);
1193             }
1194             if cfg.product_available {
1195                 m.props.set("product_available", true);
1196             }
1197             if cfg.ramdisk_available {
1198                 m.props.set("ramdisk_available", true);
1199             }
1200             if cfg.recovery_available {
1201                 m.props.set("recovery_available", true);
1202             }
1203             if cfg.vendor_available {
1204                 m.props.set("vendor_available", true);
1205             }
1206             if cfg.vendor_ramdisk_available {
1207                 m.props.set("vendor_ramdisk_available", true);
1208             }
1209             m.props.set_if_nonempty("apex_available", cfg.apex_available.clone());
1210             if let Some(min_sdk_version) = &cfg.min_sdk_version {
1211                 m.props.set("min_sdk_version", min_sdk_version.clone());
1212             }
1213         }
1214         if crate_type.is_test() {
1215             if let Some(data) =
1216                 package_cfg.test_data.get(crate_.main_src.to_string_lossy().as_ref())
1217             {
1218                 m.props.set("data", data.clone());
1219             }
1220         } else if package_cfg.no_std {
1221             m.props.set("prefer_rlib", true);
1222             m.props.set("no_stdlibs", true);
1223             let mut stdlibs = vec!["libcompiler_builtins.rust_sysroot", "libcore.rust_sysroot"];
1224             if package_cfg.alloc {
1225                 stdlibs.push("liballoc.rust_sysroot");
1226             }
1227             stdlibs.sort();
1228             m.props.set("stdlibs", stdlibs);
1229         }
1230 
1231         if let Some(visibility) = cfg.module_visibility.get(&module_name) {
1232             m.props.set("visibility", visibility.clone());
1233         }
1234 
1235         if let Some(path) = &package_cfg.add_module_block {
1236             let content = std::fs::read_to_string(path)
1237                 .with_context(|| format!("failed to read {path:?}"))?;
1238             m.props.raw_block = Some(content);
1239         }
1240 
1241         modules.push(m);
1242     }
1243     Ok(modules)
1244 }
1245 
1246 /// Convert a `Crate` into a rules.mk file.
1247 ///
1248 /// If messy business logic is necessary, prefer putting it here.
crate_to_rulesmk( crate_: &Crate, cfg: &VariantConfig, package_cfg: &PackageVariantConfig, out_files: &[String], ) -> Result<String>1249 fn crate_to_rulesmk(
1250     crate_: &Crate,
1251     cfg: &VariantConfig,
1252     package_cfg: &PackageVariantConfig,
1253     out_files: &[String],
1254 ) -> Result<String> {
1255     let mut contents = String::new();
1256 
1257     contents += "LOCAL_DIR := $(GET_LOCAL_DIR)\n";
1258     contents += "MODULE := $(LOCAL_DIR)\n";
1259     contents += &format!("MODULE_CRATE_NAME := {}\n", crate_.name);
1260 
1261     if !crate_.types.is_empty() {
1262         contents += "MODULE_RUST_CRATE_TYPES :=";
1263         for crate_type in &crate_.types {
1264             contents += match crate_type {
1265                 CrateType::Lib => " rlib",
1266                 CrateType::StaticLib => " staticlib",
1267                 CrateType::ProcMacro => " proc-macro",
1268                 _ => bail!("Cannot generate rules.mk for crate type {crate_type:?}"),
1269             };
1270         }
1271         contents += "\n";
1272     }
1273 
1274     contents += &format!("MODULE_SRCS := $(LOCAL_DIR)/{}\n", crate_.main_src.display());
1275 
1276     if !out_files.is_empty() {
1277         contents += &format!("OUT_FILES := {}\n", out_files.join(" "));
1278         contents += "BUILD_OUT_FILES := $(addprefix $(call TOBUILDDIR,$(MODULE))/,$(OUT_FILES))\n";
1279         contents += "$(BUILD_OUT_FILES): $(call TOBUILDDIR,$(MODULE))/% : $(MODULE)/out/%\n";
1280         contents += "\t@echo copying $^ to $@\n";
1281         contents += "\t@$(MKDIR)\n";
1282         contents += "\t@cp $^ $@\n\n";
1283         contents += "MODULE_RUST_ENV += OUT_DIR=$(call TOBUILDDIR,$(MODULE))\n\n";
1284         contents += "MODULE_SRCDEPS += $(BUILD_OUT_FILES)\n\n";
1285         contents += "OUT_FILES :=\n";
1286         contents += "BUILD_OUT_FILES :=\n";
1287         contents += "\n";
1288     }
1289 
1290     // crate dependencies without lib- prefix. Since paths to trusty modules may
1291     // contain hyphens, we generate the module path using the raw name output by
1292     // cargo metadata or cargo build.
1293     let mut library_deps: Vec<_> = crate_.externs.iter().map(|dep| dep.raw_name.clone()).collect();
1294     if package_cfg.no_std {
1295         contents += "MODULE_ADD_IMPLICIT_DEPS := false\n";
1296         library_deps.push("compiler_builtins".to_string());
1297         library_deps.push("core".to_string());
1298         if package_cfg.alloc {
1299             library_deps.push("alloc".to_string());
1300         }
1301     }
1302 
1303     contents += &format!("MODULE_RUST_EDITION := {}\n", crate_.edition);
1304 
1305     let mut flags = Vec::new();
1306     flags.extend(crate_.codegens.iter().map(|codegen| format!("-C {}", codegen)));
1307     flags.extend(crate_.features.iter().map(|feat| format!("--cfg 'feature=\"{feat}\"'")));
1308     flags.extend(
1309         crate_
1310             .cfgs
1311             .iter()
1312             .filter(|crate_cfg| !cfg.cfg_blocklist.contains(crate_cfg))
1313             .map(|cfg| format!("--cfg '{cfg}'")),
1314     );
1315     if !flags.is_empty() {
1316         contents += "MODULE_RUSTFLAGS += \\\n\t";
1317         contents += &flags.join(" \\\n\t");
1318         contents += "\n\n";
1319     }
1320 
1321     let mut library_deps: Vec<String> = library_deps
1322         .into_iter()
1323         .flat_map(|dep| {
1324             override_module_name(
1325                 &format!("lib{dep}"),
1326                 &package_cfg.dep_blocklist,
1327                 &BTreeMap::new(),
1328                 &RULESMK_RENAME_MAP,
1329             )
1330         })
1331         .map(|dep| {
1332             // Rewrite dependency name so it is passed to the FIND_CRATE macro
1333             // which will expand to the module path when building Trusty.
1334             if let Some(dep) = dep.strip_prefix("lib") {
1335                 format!("$(call FIND_CRATE,{dep})")
1336             } else {
1337                 dep
1338             }
1339         })
1340         .collect();
1341     library_deps.sort();
1342     library_deps.dedup();
1343     contents += "MODULE_LIBRARY_DEPS := \\\n\t";
1344     contents += &library_deps.join(" \\\n\t");
1345     contents += "\n\n";
1346 
1347     contents += "include make/library.mk\n";
1348     Ok(contents)
1349 }
1350 
1351 #[cfg(test)]
1352 mod tests {
1353     use super::*;
1354     use googletest::matchers::eq;
1355     use googletest::prelude::assert_that;
1356     use googletest::GoogleTestSupport;
1357     use std::env::{current_dir, set_current_dir};
1358     use std::fs::{self, read_to_string};
1359     use std::path::PathBuf;
1360 
1361     const TESTDATA_PATH: &str = "testdata";
1362 
1363     #[test]
group_variants_by_package()1364     fn group_variants_by_package() {
1365         let main_v1 =
1366             Crate { name: "main_v1".to_string(), package_dir: "main".into(), ..Default::default() };
1367         let main_v1_tests = Crate {
1368             name: "main_v1_tests".to_string(),
1369             package_dir: "main".into(),
1370             ..Default::default()
1371         };
1372         let other_v1 = Crate {
1373             name: "other_v1".to_string(),
1374             package_dir: "other".into(),
1375             ..Default::default()
1376         };
1377         let main_v2 =
1378             Crate { name: "main_v2".to_string(), package_dir: "main".into(), ..Default::default() };
1379         let some_v2 =
1380             Crate { name: "some_v2".to_string(), package_dir: "some".into(), ..Default::default() };
1381         let crates = vec![
1382             vec![main_v1.clone(), main_v1_tests.clone(), other_v1.clone()],
1383             vec![main_v2.clone(), some_v2.clone()],
1384         ];
1385 
1386         let module_by_package = group_by_package(crates);
1387 
1388         let expected_by_package: BTreeMap<PathBuf, Vec<Vec<Crate>>> = [
1389             ("main".into(), vec![vec![main_v1, main_v1_tests], vec![main_v2]]),
1390             ("other".into(), vec![vec![other_v1], vec![]]),
1391             ("some".into(), vec![vec![], vec![some_v2]]),
1392         ]
1393         .into_iter()
1394         .collect();
1395         assert_eq!(module_by_package, expected_by_package);
1396     }
1397 
1398     #[test]
generate_bp()1399     fn generate_bp() {
1400         for testdata_directory_path in testdata_directories() {
1401             let cfg = Config::from_json_str(
1402                 &read_to_string(testdata_directory_path.join("cargo_embargo.json"))
1403                     .expect("Failed to open cargo_embargo.json"),
1404             )
1405             .unwrap();
1406             let crates: Vec<Vec<Crate>> = serde_json::from_reader(
1407                 File::open(testdata_directory_path.join("crates.json"))
1408                     .expect("Failed to open crates.json"),
1409             )
1410             .unwrap();
1411             let expected_output =
1412                 read_to_string(testdata_directory_path.join("expected_Android.bp")).unwrap();
1413 
1414             let old_current_dir = current_dir().unwrap();
1415             set_current_dir(&testdata_directory_path).unwrap();
1416 
1417             let module_by_package = group_by_package(crates);
1418             assert_eq!(module_by_package.len(), 1);
1419             let crates = module_by_package.into_values().next().unwrap();
1420 
1421             let package_name = &crates[0][0].package_name;
1422             let def = PackageConfig::default();
1423             let package_cfg = cfg.package.get(package_name).unwrap_or(&def);
1424             let mut output = generate_android_bp_package_header(
1425                 package_name,
1426                 package_cfg,
1427                 "",
1428                 &crates,
1429                 &cfg.variants.first().unwrap().module_name_overrides,
1430             )
1431             .unwrap();
1432             for (variant_index, variant_cfg) in cfg.variants.iter().enumerate() {
1433                 let variant_crates = &crates[variant_index];
1434                 let package_name = &variant_crates[0].package_name;
1435                 let def = PackageVariantConfig::default();
1436                 let package_variant_cfg = variant_cfg.package.get(package_name).unwrap_or(&def);
1437 
1438                 output += &generate_android_bp(
1439                     variant_cfg,
1440                     package_variant_cfg,
1441                     package_name,
1442                     variant_crates,
1443                     &Vec::new(),
1444                 )
1445                 .unwrap();
1446             }
1447 
1448             assert_that!(output, eq(&expected_output), "for {}", testdata_directory_path.display());
1449 
1450             set_current_dir(old_current_dir).unwrap();
1451         }
1452     }
1453 
1454     #[test]
crate_to_bp_empty()1455     fn crate_to_bp_empty() {
1456         let c = Crate {
1457             name: "name".to_string(),
1458             package_name: "package_name".to_string(),
1459             edition: "2021".to_string(),
1460             types: vec![],
1461             ..Default::default()
1462         };
1463         let cfg = VariantConfig { ..Default::default() };
1464         let package_cfg = PackageVariantConfig { ..Default::default() };
1465         let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1466 
1467         assert_eq!(modules, vec![]);
1468     }
1469 
1470     #[test]
crate_to_bp_minimal()1471     fn crate_to_bp_minimal() {
1472         let c = Crate {
1473             name: "name".to_string(),
1474             package_name: "package_name".to_string(),
1475             edition: "2021".to_string(),
1476             types: vec![CrateType::Lib],
1477             ..Default::default()
1478         };
1479         let cfg = VariantConfig { ..Default::default() };
1480         let package_cfg = PackageVariantConfig { ..Default::default() };
1481         let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1482 
1483         assert_eq!(
1484             modules,
1485             vec![BpModule {
1486                 module_type: "rust_library".to_string(),
1487                 props: BpProperties {
1488                     map: [
1489                         (
1490                             "apex_available".to_string(),
1491                             BpValue::List(vec![
1492                                 BpValue::String("//apex_available:platform".to_string()),
1493                                 BpValue::String("//apex_available:anyapex".to_string()),
1494                             ])
1495                         ),
1496                         ("cargo_env_compat".to_string(), BpValue::Bool(true)),
1497                         ("crate_name".to_string(), BpValue::String("name".to_string())),
1498                         ("edition".to_string(), BpValue::String("2021".to_string())),
1499                         ("host_supported".to_string(), BpValue::Bool(true)),
1500                         ("name".to_string(), BpValue::String("libname".to_string())),
1501                         ("product_available".to_string(), BpValue::Bool(true)),
1502                         ("crate_root".to_string(), BpValue::String("".to_string())),
1503                         ("vendor_available".to_string(), BpValue::Bool(true)),
1504                     ]
1505                     .into_iter()
1506                     .collect(),
1507                     raw_block: None
1508                 }
1509             }]
1510         );
1511     }
1512 
1513     #[test]
escape_cfgs()1514     fn escape_cfgs() {
1515         let c = Crate {
1516             name: "name".to_string(),
1517             package_name: "package_name".to_string(),
1518             edition: "2021".to_string(),
1519             types: vec![CrateType::Lib],
1520             cfgs: vec![r#"foo="bar""#.to_string()],
1521             ..Default::default()
1522         };
1523         let cfg = VariantConfig { ..Default::default() };
1524         let package_cfg = PackageVariantConfig { ..Default::default() };
1525         let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1526 
1527         assert_eq!(
1528             modules[0].props.map.get("cfgs"),
1529             Some(&BpValue::List(vec![BpValue::String(r#"foo=\"bar\""#.to_string())]))
1530         );
1531     }
1532 
1533     #[test]
crate_to_bp_rename()1534     fn crate_to_bp_rename() {
1535         let c = Crate {
1536             name: "ash".to_string(),
1537             package_name: "package_name".to_string(),
1538             edition: "2021".to_string(),
1539             types: vec![CrateType::Lib],
1540             ..Default::default()
1541         };
1542         let cfg = VariantConfig { ..Default::default() };
1543         let package_cfg = PackageVariantConfig { ..Default::default() };
1544         let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1545 
1546         assert_eq!(
1547             modules,
1548             vec![BpModule {
1549                 module_type: "rust_library".to_string(),
1550                 props: BpProperties {
1551                     map: [
1552                         (
1553                             "apex_available".to_string(),
1554                             BpValue::List(vec![
1555                                 BpValue::String("//apex_available:platform".to_string()),
1556                                 BpValue::String("//apex_available:anyapex".to_string()),
1557                             ])
1558                         ),
1559                         ("cargo_env_compat".to_string(), BpValue::Bool(true)),
1560                         ("crate_name".to_string(), BpValue::String("ash".to_string())),
1561                         ("edition".to_string(), BpValue::String("2021".to_string())),
1562                         ("host_supported".to_string(), BpValue::Bool(true)),
1563                         ("name".to_string(), BpValue::String("libash_rust".to_string())),
1564                         ("product_available".to_string(), BpValue::Bool(true)),
1565                         ("crate_root".to_string(), BpValue::String("".to_string())),
1566                         ("vendor_available".to_string(), BpValue::Bool(true)),
1567                     ]
1568                     .into_iter()
1569                     .collect(),
1570                     raw_block: None
1571                 }
1572             }]
1573         );
1574     }
1575 
1576     /// Returns a list of directories containing test data.
1577     ///
1578     /// Each directory under `testdata/` contains a single test case.
testdata_directories() -> Vec<PathBuf>1579     pub fn testdata_directories() -> Vec<PathBuf> {
1580         fs::read_dir(TESTDATA_PATH)
1581             .expect("Failed to read testdata directory")
1582             .filter_map(|entry| {
1583                 let entry = entry.expect("Error reading testdata directory entry");
1584                 if entry
1585                     .file_type()
1586                     .expect("Error getting metadata for testdata subdirectory")
1587                     .is_dir()
1588                 {
1589                     Some(entry.path())
1590                 } else {
1591                     None
1592                 }
1593             })
1594             .collect()
1595     }
1596 }
1597