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 envs = if cfg.extra_cfg.is_empty() {
517 vec![]
518 } else {
519 vec![(
520 "RUSTFLAGS",
521 cfg.extra_cfg
522 .iter()
523 .map(|cfg_flag| format!("--cfg {}", cfg_flag))
524 .collect::<Vec<_>>()
525 .join(" "),
526 )]
527 };
528
529 // cargo build
530 cargo_out += &run_cargo(
531 Command::new("cargo")
532 .envs(envs.clone())
533 .args(["build", "--target", default_target])
534 .args(verbose_args)
535 .arg("--target-dir")
536 .arg(&target_dir)
537 .args(&workspace_args)
538 .args(&feature_args),
539 true,
540 )?;
541
542 if cfg.tests {
543 // cargo build --tests
544 cargo_out += &run_cargo(
545 Command::new("cargo")
546 .envs(envs.clone())
547 .args(["build", "--target", default_target, "--tests"])
548 .args(verbose_args)
549 .arg("--target-dir")
550 .arg(&target_dir)
551 .args(&workspace_args)
552 .args(&feature_args),
553 true,
554 )?;
555 // cargo test -- --list
556 cargo_out += &run_cargo(
557 Command::new("cargo")
558 .envs(envs)
559 .args(["test", "--target", default_target])
560 .arg("--target-dir")
561 .arg(&target_dir)
562 .args(&workspace_args)
563 .args(&feature_args)
564 .args(["--", "--list"]),
565 true,
566 )?;
567 }
568 }
569
570 Ok(CargoOutput { cargo_metadata, cargo_out })
571 }
572
573 /// Read and return license and other header lines from a build file.
574 ///
575 /// Skips initial comment lines, then returns all lines before the first line
576 /// starting with `rust_`, `genrule {`, or `LOCAL_DIR`.
577 ///
578 /// If `path` could not be read and a license is required, return a
579 /// placeholder license TODO line.
read_license_header(path: &Path, require_license: bool) -> Result<String>580 fn read_license_header(path: &Path, require_license: bool) -> Result<String> {
581 // Keep the old license header.
582 match std::fs::read_to_string(path) {
583 Ok(s) => Ok(s
584 .lines()
585 .skip_while(|l| l.starts_with("//") || l.starts_with('#'))
586 .take_while(|l| {
587 !l.starts_with("rust_")
588 && !l.starts_with("genrule {")
589 && !l.starts_with("LOCAL_DIR")
590 })
591 .collect::<Vec<&str>>()
592 .join("\n")),
593 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
594 let placeholder = if require_license {
595 "// DO NOT SUBMIT: Add license before submitting.\n"
596 } else {
597 ""
598 };
599 Ok(placeholder.to_string())
600 }
601 Err(e) => Err(anyhow!("error when reading {path:?}: {e}")),
602 }
603 }
604
605 /// Create the build file for `package_dir`.
606 ///
607 /// `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<()>608 fn write_build_files(
609 cfg: &Config,
610 package_name: &str,
611 package_dir: PathBuf,
612 crates: &[Vec<Crate>],
613 out_files: &[Vec<PathBuf>],
614 ) -> Result<()> {
615 assert_eq!(crates.len(), out_files.len());
616
617 let mut bp_contents = String::new();
618 let mut mk_contents = String::new();
619 for (variant_index, variant_config) in cfg.variants.iter().enumerate() {
620 let variant_crates = &crates[variant_index];
621 let def = PackageVariantConfig::default();
622 let package_variant_cfg = variant_config.package.get(package_name).unwrap_or(&def);
623
624 // If `copy_out` is enabled and there are any generated out files for the package, copy them to
625 // the appropriate directory.
626 if package_variant_cfg.copy_out && !out_files[variant_index].is_empty() {
627 let out_dir = package_dir.join("out");
628 if !out_dir.exists() {
629 std::fs::create_dir(&out_dir).expect("failed to create out dir");
630 }
631
632 for f in out_files[variant_index].iter() {
633 let dest = out_dir.join(f.file_name().unwrap());
634 std::fs::copy(f, dest).expect("failed to copy out file");
635 }
636 }
637
638 if variant_config.generate_androidbp {
639 bp_contents += &generate_android_bp(
640 variant_config,
641 package_variant_cfg,
642 package_name,
643 variant_crates,
644 &out_files[variant_index],
645 )?;
646 }
647 if variant_config.generate_rulesmk {
648 mk_contents += &generate_rules_mk(
649 variant_config,
650 package_variant_cfg,
651 package_name,
652 variant_crates,
653 &out_files[variant_index],
654 )?;
655 }
656 }
657 if !mk_contents.is_empty() {
658 // If rules.mk is generated, then make it accessible via dirgroup.
659 bp_contents += &generate_android_bp_for_rules_mk(package_name)?;
660 }
661
662 let def = PackageConfig::default();
663 let package_cfg = cfg.package.get(package_name).unwrap_or(&def);
664 if let Some(path) = &package_cfg.add_toplevel_block {
665 bp_contents +=
666 &std::fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
667 bp_contents += "\n";
668 }
669 if !bp_contents.is_empty() {
670 let output_path = package_dir.join("Android.bp");
671 let package_header = generate_android_bp_package_header(
672 package_name,
673 package_cfg,
674 read_license_header(&output_path, true)?.trim(),
675 crates,
676 &cfg.variants.first().unwrap().module_name_overrides,
677 )?;
678 let bp_contents = package_header + &bp_contents;
679 write_format_android_bp(&output_path, &bp_contents, package_cfg.patch.as_deref())?;
680 }
681 if !mk_contents.is_empty() {
682 let output_path = package_dir.join("rules.mk");
683 let mk_contents = "# This file is generated by cargo_embargo.\n".to_owned()
684 + "# Do not modify this file after the LOCAL_DIR line\n"
685 + "# because the changes will be overridden on upgrade.\n"
686 + "# Content before the first line starting with LOCAL_DIR is preserved.\n"
687 + read_license_header(&output_path, false)?.trim()
688 + "\n"
689 + &mk_contents;
690 File::create(&output_path)?.write_all(mk_contents.as_bytes())?;
691 if let Some(patch) = package_cfg.rulesmk_patch.as_deref() {
692 apply_patch_file(&output_path, patch)?;
693 }
694 }
695
696 Ok(())
697 }
698
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>699 fn generate_android_bp_package_header(
700 package_name: &str,
701 package_cfg: &PackageConfig,
702 license_header: &str,
703 crates: &[Vec<Crate>],
704 module_name_overrides: &BTreeMap<String, String>,
705 ) -> Result<String> {
706 let crates = crates.iter().flatten().collect::<Vec<_>>();
707 if let Some(first) = crates.first() {
708 if let Some(license) = first.license.as_ref() {
709 if crates.iter().all(|c| c.license.as_ref() == Some(license)) {
710 let mut modules = Vec::new();
711 let licenses = choose_licenses(license)?;
712
713 let default_license_name = format!("external_rust_crates_{}_license", package_name);
714
715 let license_name = match override_module_name(
716 &default_license_name,
717 &[],
718 module_name_overrides,
719 &RENAME_MAP,
720 ) {
721 Some(x) => x,
722 None => default_license_name,
723 };
724
725 let mut package_module = BpModule::new("package".to_string());
726 package_module.props.set("default_team", "trendy_team_android_rust");
727 package_module.props.set("default_applicable_licenses", vec![license_name.clone()]);
728 modules.push(package_module);
729
730 let mut license_module = BpModule::new("license".to_string());
731 license_module.props.set("name", license_name);
732 license_module.props.set("visibility", vec![":__subpackages__"]);
733 license_module.props.set(
734 "license_kinds",
735 licenses
736 .into_iter()
737 .map(|license| format!("SPDX-license-identifier-{}", license))
738 .collect::<Vec<_>>(),
739 );
740 let license_text = package_cfg.license_text.clone().unwrap_or_else(|| {
741 vec![first.license_file.as_deref().unwrap_or("LICENSE").to_string()]
742 });
743 license_module.props.set("license_text", license_text);
744 modules.push(license_module);
745
746 let mut bp_contents = "// This file is generated by cargo_embargo.\n".to_owned()
747 + "// Do not modify this file because the changes will be overridden on upgrade.\n\n";
748 for m in modules {
749 m.write(&mut bp_contents)?;
750 bp_contents += "\n";
751 }
752 return Ok(bp_contents);
753 } else {
754 eprintln!("Crates have different licenses.");
755 }
756 }
757 }
758
759 Ok("// This file is generated by cargo_embargo.\n".to_owned()
760 + "// Do not modify this file after the first \"rust_*\" or \"genrule\" module\n"
761 + "// because the changes will be overridden on upgrade.\n"
762 + "// Content before the first \"rust_*\" or \"genrule\" module is preserved.\n\n"
763 + license_header
764 + "\n")
765 }
766
767 /// Given an SPDX license expression that may offer a choice between several licenses, choose one or
768 /// more to use.
choose_licenses(license: &str) -> Result<Vec<&str>>769 fn choose_licenses(license: &str) -> Result<Vec<&str>> {
770 Ok(match license {
771 // Variations on "MIT OR Apache-2.0"
772 "MIT OR Apache-2.0" => vec!["Apache-2.0"],
773 "Apache-2.0 OR MIT" => vec!["Apache-2.0"],
774 "MIT/Apache-2.0" => vec!["Apache-2.0"],
775 "Apache-2.0/MIT" => vec!["Apache-2.0"],
776 "Apache-2.0 / MIT" => vec!["Apache-2.0"],
777
778 // Variations on "BSD-* OR Apache-2.0"
779 "Apache-2.0 OR BSD-3-Clause" => vec!["Apache-2.0"],
780 "Apache-2.0 or BSD-3-Clause" => vec!["Apache-2.0"],
781 "BSD-3-Clause OR Apache-2.0" => vec!["Apache-2.0"],
782
783 // Variations on "BSD-* OR MIT OR Apache-2.0"
784 "BSD-3-Clause OR MIT OR Apache-2.0" => vec!["Apache-2.0"],
785 "BSD-2-Clause OR Apache-2.0 OR MIT" => vec!["Apache-2.0"],
786
787 // Variations on "Zlib OR MIT OR Apache-2.0"
788 "Zlib OR Apache-2.0 OR MIT" => vec!["Apache-2.0"],
789 "MIT OR Apache-2.0 OR Zlib" => vec!["Apache-2.0"],
790
791 // Variations on "Apache-2.0 OR *"
792 "Apache-2.0 OR BSL-1.0" => vec!["Apache-2.0"],
793 "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" => vec!["Apache-2.0"],
794
795 // Variations on "Unlicense OR MIT"
796 "Unlicense OR MIT" => vec!["MIT"],
797 "Unlicense/MIT" => vec!["MIT"],
798
799 // Multiple licenses.
800 "(MIT OR Apache-2.0) AND Unicode-DFS-2016" => vec!["Apache-2.0", "Unicode-DFS-2016"],
801 "MIT AND BSD-3-Clause" => vec!["BSD-3-Clause", "MIT"],
802 // Usually we interpret "/" as "OR", but in the case of libfuzzer-sys, closer
803 // inspection of the terms indicates the correct interpretation is "(MIT OR APACHE) AND NCSA".
804 "MIT/Apache-2.0/NCSA" => vec!["Apache-2.0", "NCSA"],
805
806 // Other cases.
807 "MIT OR LGPL-3.0-or-later" => vec!["MIT"],
808 "MIT/BSD-3-Clause" => vec!["MIT"],
809
810 "LGPL-2.1-only OR BSD-2-Clause" => vec!["BSD-2-Clause"],
811 _ => {
812 // If there is whitespace, it is probably an SPDX expression.
813 if license.contains(char::is_whitespace) {
814 bail!("Unrecognized license: {license}");
815 }
816 vec![license]
817 }
818 })
819 }
820
821 /// Generates and returns a Soong Blueprint for the given set of crates, for a single variant of a
822 /// package.
generate_android_bp( cfg: &VariantConfig, package_cfg: &PackageVariantConfig, package_name: &str, crates: &[Crate], out_files: &[PathBuf], ) -> Result<String>823 fn generate_android_bp(
824 cfg: &VariantConfig,
825 package_cfg: &PackageVariantConfig,
826 package_name: &str,
827 crates: &[Crate],
828 out_files: &[PathBuf],
829 ) -> Result<String> {
830 let mut bp_contents = String::new();
831
832 let mut modules = Vec::new();
833
834 let extra_srcs = if package_cfg.copy_out && !out_files.is_empty() {
835 let outs: Vec<String> = out_files
836 .iter()
837 .map(|f| f.file_name().unwrap().to_str().unwrap().to_string())
838 .collect();
839
840 let mut m = BpModule::new("genrule".to_string());
841 if let Some(module_name) = override_module_name(
842 &format!("copy_{}_build_out", package_name),
843 &cfg.module_blocklist,
844 &cfg.module_name_overrides,
845 &RENAME_MAP,
846 ) {
847 m.props.set("name", module_name.clone());
848 m.props.set("srcs", vec!["out/*"]);
849 m.props.set("cmd", "cp $(in) $(genDir)");
850 m.props.set("out", outs);
851 modules.push(m);
852
853 vec![":".to_string() + &module_name]
854 } else {
855 vec![]
856 }
857 } else {
858 vec![]
859 };
860
861 for c in crates {
862 modules.extend(crate_to_bp_modules(c, cfg, package_cfg, &extra_srcs).with_context(
863 || {
864 format!(
865 "failed to generate bp module for crate \"{}\" with package name \"{}\"",
866 c.name, c.package_name
867 )
868 },
869 )?);
870 }
871
872 // In some cases there are nearly identical rustc invocations that that get processed into
873 // identical BP modules. So far, dedup'ing them is a good enough fix. At some point we might
874 // need something more complex, maybe like cargo2android's logic for merging crates.
875 modules.sort();
876 modules.dedup();
877
878 modules.sort_by_key(|m| m.props.get_string("name").unwrap().to_string());
879 for m in modules {
880 m.write(&mut bp_contents)?;
881 bp_contents += "\n";
882 }
883 Ok(bp_contents)
884 }
885
886 /// 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>887 fn generate_rules_mk(
888 cfg: &VariantConfig,
889 package_cfg: &PackageVariantConfig,
890 package_name: &str,
891 crates: &[Crate],
892 out_files: &[PathBuf],
893 ) -> Result<String> {
894 let out_files = if package_cfg.copy_out && !out_files.is_empty() {
895 out_files.iter().map(|f| f.file_name().unwrap().to_str().unwrap().to_string()).collect()
896 } else {
897 vec![]
898 };
899
900 let crates: Vec<_> = crates
901 .iter()
902 .filter(|c| {
903 if c.types.contains(&CrateType::Bin) {
904 eprintln!("WARNING: skipped generation of rules.mk for binary crate: {}", c.name);
905 false
906 } else if c.types.iter().any(|t| t.is_test()) {
907 // Test build file generation is not yet implemented
908 eprintln!("WARNING: skipped generation of rules.mk for test crate: {}", c.name);
909 false
910 } else {
911 true
912 }
913 })
914 .collect();
915 let [crate_] = &crates[..] else {
916 bail!(
917 "Expected exactly one library crate for package {package_name} when generating \
918 rules.mk, found: {crates:?}"
919 );
920 };
921 crate_to_rulesmk(crate_, cfg, package_cfg, &out_files).with_context(|| {
922 format!(
923 "failed to generate rules.mk for crate \"{}\" with package name \"{}\"",
924 crate_.name, crate_.package_name
925 )
926 })
927 }
928
929 /// Generates and returns a Soong Blueprint for a Trusty rules.mk
generate_android_bp_for_rules_mk(package_name: &str) -> Result<String>930 fn generate_android_bp_for_rules_mk(package_name: &str) -> Result<String> {
931 let mut bp_contents = String::new();
932
933 let mut m = BpModule::new("dirgroup".to_string());
934 m.props.set("name", format!("trusty_dirgroup_external_rust_crates_{}", package_name));
935 m.props.set("dirs", vec!["."]);
936 m.props.set("visibility", vec!["//trusty/vendor/google/aosp/scripts"]);
937
938 m.write(&mut bp_contents)?;
939 bp_contents += "\n";
940
941 Ok(bp_contents)
942 }
943
944 /// Apply patch from `patch_path` to file `output_path`.
945 ///
946 /// Warns but still returns ok if the patch did not cleanly apply,
apply_patch_file(output_path: &Path, patch_path: &Path) -> Result<()>947 fn apply_patch_file(output_path: &Path, patch_path: &Path) -> Result<()> {
948 let patch_output = Command::new("patch")
949 .arg("-s")
950 .arg("--no-backup-if-mismatch")
951 .arg(output_path)
952 .arg(patch_path)
953 .output()
954 .context("Running patch")?;
955 if !patch_output.status.success() {
956 let stdout = String::from_utf8(patch_output.stdout)?;
957 let stderr = String::from_utf8(patch_output.stderr)?;
958 // These errors will cause the cargo_embargo command to fail, but not yet!
959 bail!("failed to apply patch {patch_path:?}:\n\nout:\n{stdout}\n\nerr:\n{stderr}");
960 }
961 Ok(())
962 }
963
964 /// Writes the given contents to the given `Android.bp` file, formats it with `bpfmt`, and applies
965 /// the patch if there is one.
write_format_android_bp( bp_path: &Path, bp_contents: &str, patch_path: Option<&Path>, ) -> Result<()>966 fn write_format_android_bp(
967 bp_path: &Path,
968 bp_contents: &str,
969 patch_path: Option<&Path>,
970 ) -> Result<()> {
971 File::create(bp_path)?.write_all(bp_contents.as_bytes())?;
972
973 let bpfmt_output =
974 Command::new("bpfmt").arg("-w").arg(bp_path).output().context("Running bpfmt")?;
975 if !bpfmt_output.status.success() {
976 eprintln!(
977 "WARNING: bpfmt -w {:?} failed before patch: {}",
978 bp_path,
979 String::from_utf8_lossy(&bpfmt_output.stderr)
980 );
981 }
982
983 if let Some(patch_path) = patch_path {
984 apply_patch_file(bp_path, patch_path)?;
985 // Re-run bpfmt after the patch so
986 let bpfmt_output = Command::new("bpfmt")
987 .arg("-w")
988 .arg(bp_path)
989 .output()
990 .context("Running bpfmt after patch")?;
991 if !bpfmt_output.status.success() {
992 eprintln!(
993 "WARNING: bpfmt -w {:?} failed after patch: {}",
994 bp_path,
995 String::from_utf8_lossy(&bpfmt_output.stderr)
996 );
997 }
998 }
999
1000 Ok(())
1001 }
1002
1003 /// Convert a `Crate` into `BpModule`s.
1004 ///
1005 /// 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>>1006 fn crate_to_bp_modules(
1007 crate_: &Crate,
1008 cfg: &VariantConfig,
1009 package_cfg: &PackageVariantConfig,
1010 extra_srcs: &[String],
1011 ) -> Result<Vec<BpModule>> {
1012 let mut modules = Vec::new();
1013 for crate_type in &crate_.types {
1014 let host = if package_cfg.device_supported { "" } else { "_host" };
1015 let rlib = if package_cfg.force_rlib { "_rlib" } else { "" };
1016 let (module_type, module_name) = match crate_type {
1017 CrateType::Bin => ("rust_binary".to_string() + host, crate_.name.clone()),
1018 CrateType::Lib | CrateType::RLib => {
1019 let stem = "lib".to_string() + &crate_.name;
1020 ("rust_library".to_string() + host + rlib, stem)
1021 }
1022 CrateType::DyLib => {
1023 let stem = "lib".to_string() + &crate_.name;
1024 ("rust_library".to_string() + host + "_dylib", stem + "_dylib")
1025 }
1026 CrateType::CDyLib => {
1027 let stem = "lib".to_string() + &crate_.name;
1028 ("rust_ffi".to_string() + host + "_shared", stem + "_shared")
1029 }
1030 CrateType::StaticLib => {
1031 let stem = "lib".to_string() + &crate_.name;
1032 ("rust_ffi".to_string() + host + "_static", stem + "_static")
1033 }
1034 CrateType::ProcMacro => {
1035 let stem = "lib".to_string() + &crate_.name;
1036 ("rust_proc_macro".to_string(), stem)
1037 }
1038 CrateType::Test | CrateType::TestNoHarness => {
1039 let suffix = crate_.main_src.to_string_lossy().into_owned();
1040 let suffix = suffix.replace('/', "_").replace(".rs", "");
1041 let stem = crate_.package_name.clone() + "_test_" + &suffix;
1042 if crate_.empty_test {
1043 return Ok(Vec::new());
1044 }
1045 if crate_type == &CrateType::TestNoHarness {
1046 eprintln!(
1047 "WARNING: ignoring test \"{}\" with harness=false. not supported yet",
1048 stem
1049 );
1050 return Ok(Vec::new());
1051 }
1052 ("rust_test".to_string() + host, stem)
1053 }
1054 };
1055
1056 let mut m = BpModule::new(module_type.clone());
1057 let Some(module_name) = override_module_name(
1058 &module_name,
1059 &cfg.module_blocklist,
1060 &cfg.module_name_overrides,
1061 &RENAME_MAP,
1062 ) else {
1063 continue;
1064 };
1065 if matches!(
1066 crate_type,
1067 CrateType::Lib
1068 | CrateType::RLib
1069 | CrateType::DyLib
1070 | CrateType::CDyLib
1071 | CrateType::StaticLib
1072 ) && !module_name.starts_with(&format!("lib{}", crate_.name))
1073 {
1074 bail!("Module name must start with lib{} but was {}", crate_.name, module_name);
1075 }
1076 m.props.set("name", module_name.clone());
1077
1078 if let Some(defaults) = &cfg.global_defaults {
1079 m.props.set("defaults", vec![defaults.clone()]);
1080 }
1081
1082 if package_cfg.host_supported
1083 && package_cfg.device_supported
1084 && module_type != "rust_proc_macro"
1085 {
1086 m.props.set("host_supported", true);
1087 }
1088
1089 if module_type != "rust_proc_macro" {
1090 if package_cfg.host_supported && !package_cfg.host_cross_supported {
1091 m.props.set("host_cross_supported", false);
1092 } else if crate_.externs.iter().any(|extern_dep| extern_dep.name == "proc_macro2") {
1093 // proc_macro2 is host_cross_supported: false.
1094 // If there's a dependency on it, then we shouldn't build for HostCross.
1095 m.props.set("host_cross_supported", false);
1096 } else if crate_.package_name == "proc-macro2" {
1097 m.props.set("host_cross_supported", false);
1098 }
1099 }
1100
1101 if !crate_type.is_test() && package_cfg.host_supported && package_cfg.host_first_multilib {
1102 m.props.set("compile_multilib", "first");
1103 }
1104 if crate_type.is_c_library() {
1105 m.props.set_if_nonempty("include_dirs", package_cfg.exported_c_header_dir.clone());
1106 }
1107
1108 m.props.set("crate_name", crate_.name.clone());
1109 m.props.set("cargo_env_compat", true);
1110
1111 if let Some(version) = &crate_.version {
1112 m.props.set("cargo_pkg_version", version.clone());
1113 }
1114
1115 if crate_.types.contains(&CrateType::Test) {
1116 m.props.set("test_suites", vec!["general-tests"]);
1117 m.props.set("auto_gen_config", true);
1118 if package_cfg.host_supported {
1119 m.props.object("test_options").set("unit_test", !package_cfg.no_presubmit);
1120 }
1121 }
1122
1123 m.props.set("crate_root", crate_.main_src.clone());
1124 m.props.set_if_nonempty("srcs", extra_srcs.to_owned());
1125
1126 m.props.set("edition", crate_.edition.clone());
1127 m.props.set_if_nonempty("features", crate_.features.clone());
1128 m.props.set_if_nonempty(
1129 "cfgs",
1130 crate_
1131 .cfgs
1132 .clone()
1133 .into_iter()
1134 .filter(|crate_cfg| !cfg.cfg_blocklist.contains(crate_cfg))
1135 .collect(),
1136 );
1137
1138 let mut flags = Vec::new();
1139 if !crate_.cap_lints.is_empty() {
1140 flags.push(crate_.cap_lints.clone());
1141 }
1142 flags.extend(crate_.codegens.iter().map(|codegen| format!("-C {}", codegen)));
1143 m.props.set_if_nonempty("flags", flags);
1144
1145 let mut rust_libs = Vec::new();
1146 let mut proc_macro_libs = Vec::new();
1147 let mut aliases = Vec::new();
1148 for extern_dep in &crate_.externs {
1149 match extern_dep.extern_type {
1150 ExternType::Rust => rust_libs.push(extern_dep.lib_name.clone()),
1151 ExternType::ProcMacro => proc_macro_libs.push(extern_dep.lib_name.clone()),
1152 }
1153 if extern_dep.name != extern_dep.lib_name {
1154 aliases.push(format!("{}:{}", extern_dep.lib_name, extern_dep.name));
1155 }
1156 }
1157
1158 // Add "lib" prefix and apply name overrides.
1159 let process_lib_deps = |libs: Vec<String>| -> Vec<String> {
1160 let mut result = Vec::new();
1161 for x in libs {
1162 let module_name = "lib".to_string() + x.as_str();
1163 if let Some(module_name) = override_module_name(
1164 &module_name,
1165 &package_cfg.dep_blocklist,
1166 &cfg.module_name_overrides,
1167 &RENAME_MAP,
1168 ) {
1169 result.push(module_name);
1170 }
1171 }
1172 result.sort();
1173 result.dedup();
1174 result
1175 };
1176 m.props.set_if_nonempty("rustlibs", process_lib_deps(rust_libs));
1177 m.props.set_if_nonempty("proc_macros", process_lib_deps(proc_macro_libs));
1178 let (whole_static_libs, static_libs) = process_lib_deps(crate_.static_libs.clone())
1179 .into_iter()
1180 .partition(|static_lib| package_cfg.whole_static_libs.contains(static_lib));
1181 m.props.set_if_nonempty("static_libs", static_libs);
1182 m.props.set_if_nonempty("whole_static_libs", whole_static_libs);
1183 m.props.set_if_nonempty("shared_libs", process_lib_deps(crate_.shared_libs.clone()));
1184 m.props.set_if_nonempty("aliases", aliases);
1185
1186 if package_cfg.device_supported {
1187 if !crate_type.is_test() {
1188 if cfg.native_bridge_supported {
1189 m.props.set("native_bridge_supported", true);
1190 }
1191 if cfg.product_available {
1192 m.props.set("product_available", true);
1193 }
1194 if cfg.ramdisk_available {
1195 m.props.set("ramdisk_available", true);
1196 }
1197 if cfg.recovery_available {
1198 m.props.set("recovery_available", true);
1199 }
1200 if cfg.vendor_available {
1201 m.props.set("vendor_available", true);
1202 }
1203 if cfg.vendor_ramdisk_available {
1204 m.props.set("vendor_ramdisk_available", true);
1205 }
1206 }
1207 if crate_type.is_library() {
1208 m.props.set_if_nonempty("apex_available", cfg.apex_available.clone());
1209 if let Some(min_sdk_version) = &cfg.min_sdk_version {
1210 m.props.set("min_sdk_version", min_sdk_version.clone());
1211 }
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 if !crate_.cap_lints.is_empty() {
1307 flags.push(crate_.cap_lints.clone());
1308 }
1309 flags.extend(crate_.codegens.iter().map(|codegen| format!("-C {}", codegen)));
1310 flags.extend(crate_.features.iter().map(|feat| format!("--cfg 'feature=\"{feat}\"'")));
1311 flags.extend(
1312 crate_
1313 .cfgs
1314 .iter()
1315 .filter(|crate_cfg| !cfg.cfg_blocklist.contains(crate_cfg))
1316 .map(|cfg| format!("--cfg '{cfg}'")),
1317 );
1318 if !flags.is_empty() {
1319 contents += "MODULE_RUSTFLAGS += \\\n\t";
1320 contents += &flags.join(" \\\n\t");
1321 contents += "\n\n";
1322 }
1323
1324 let mut library_deps: Vec<String> = library_deps
1325 .into_iter()
1326 .flat_map(|dep| {
1327 override_module_name(
1328 &format!("lib{dep}"),
1329 &package_cfg.dep_blocklist,
1330 &cfg.module_name_overrides,
1331 &RULESMK_RENAME_MAP,
1332 )
1333 })
1334 .map(|dep| {
1335 // Rewrite dependency name so it is passed to the FIND_CRATE macro
1336 // which will expand to the module path when building Trusty.
1337 if let Some(dep) = dep.strip_prefix("lib") {
1338 format!("$(call FIND_CRATE,{dep})")
1339 } else {
1340 dep
1341 }
1342 })
1343 .collect();
1344 library_deps.sort();
1345 library_deps.dedup();
1346 contents += "MODULE_LIBRARY_DEPS := \\\n\t";
1347 contents += &library_deps.join(" \\\n\t");
1348 contents += "\n\n";
1349
1350 contents += "include make/library.mk\n";
1351 Ok(contents)
1352 }
1353
1354 #[cfg(test)]
1355 mod tests {
1356 use super::*;
1357 use googletest::matchers::eq;
1358 use googletest::prelude::assert_that;
1359 use std::env::{current_dir, set_current_dir};
1360 use std::fs::{self, read_to_string};
1361 use std::path::PathBuf;
1362
1363 const TESTDATA_PATH: &str = "testdata";
1364
1365 #[test]
group_variants_by_package()1366 fn group_variants_by_package() {
1367 let main_v1 =
1368 Crate { name: "main_v1".to_string(), package_dir: "main".into(), ..Default::default() };
1369 let main_v1_tests = Crate {
1370 name: "main_v1_tests".to_string(),
1371 package_dir: "main".into(),
1372 ..Default::default()
1373 };
1374 let other_v1 = Crate {
1375 name: "other_v1".to_string(),
1376 package_dir: "other".into(),
1377 ..Default::default()
1378 };
1379 let main_v2 =
1380 Crate { name: "main_v2".to_string(), package_dir: "main".into(), ..Default::default() };
1381 let some_v2 =
1382 Crate { name: "some_v2".to_string(), package_dir: "some".into(), ..Default::default() };
1383 let crates = vec![
1384 vec![main_v1.clone(), main_v1_tests.clone(), other_v1.clone()],
1385 vec![main_v2.clone(), some_v2.clone()],
1386 ];
1387
1388 let module_by_package = group_by_package(crates);
1389
1390 let expected_by_package: BTreeMap<PathBuf, Vec<Vec<Crate>>> = [
1391 ("main".into(), vec![vec![main_v1, main_v1_tests], vec![main_v2]]),
1392 ("other".into(), vec![vec![other_v1], vec![]]),
1393 ("some".into(), vec![vec![], vec![some_v2]]),
1394 ]
1395 .into_iter()
1396 .collect();
1397 assert_eq!(module_by_package, expected_by_package);
1398 }
1399
1400 #[test]
generate_bp()1401 fn generate_bp() {
1402 for testdata_directory_path in testdata_directories() {
1403 let cfg = Config::from_json_str(
1404 &read_to_string(testdata_directory_path.join("cargo_embargo.json"))
1405 .expect("Failed to open cargo_embargo.json"),
1406 )
1407 .unwrap();
1408 let crates: Vec<Vec<Crate>> = serde_json::from_reader(
1409 File::open(testdata_directory_path.join("crates.json"))
1410 .expect("Failed to open crates.json"),
1411 )
1412 .unwrap();
1413 let expected_output =
1414 read_to_string(testdata_directory_path.join("expected_Android.bp")).unwrap();
1415
1416 let old_current_dir = current_dir().unwrap();
1417 set_current_dir(&testdata_directory_path).unwrap();
1418
1419 let module_by_package = group_by_package(crates);
1420 assert_eq!(module_by_package.len(), 1);
1421 let crates = module_by_package.into_values().next().unwrap();
1422
1423 let package_name = &crates[0][0].package_name;
1424 let def = PackageConfig::default();
1425 let package_cfg = cfg.package.get(package_name).unwrap_or(&def);
1426 let mut output = generate_android_bp_package_header(
1427 package_name,
1428 package_cfg,
1429 "",
1430 &crates,
1431 &cfg.variants.first().unwrap().module_name_overrides,
1432 )
1433 .unwrap();
1434 for (variant_index, variant_cfg) in cfg.variants.iter().enumerate() {
1435 let variant_crates = &crates[variant_index];
1436 let package_name = &variant_crates[0].package_name;
1437 let def = PackageVariantConfig::default();
1438 let package_variant_cfg = variant_cfg.package.get(package_name).unwrap_or(&def);
1439
1440 output += &generate_android_bp(
1441 variant_cfg,
1442 package_variant_cfg,
1443 package_name,
1444 variant_crates,
1445 &Vec::new(),
1446 )
1447 .unwrap();
1448 }
1449
1450 assert_that!(output, eq(expected_output));
1451
1452 set_current_dir(old_current_dir).unwrap();
1453 }
1454 }
1455
1456 #[test]
crate_to_bp_empty()1457 fn crate_to_bp_empty() {
1458 let c = Crate {
1459 name: "name".to_string(),
1460 package_name: "package_name".to_string(),
1461 edition: "2021".to_string(),
1462 types: vec![],
1463 ..Default::default()
1464 };
1465 let cfg = VariantConfig { ..Default::default() };
1466 let package_cfg = PackageVariantConfig { ..Default::default() };
1467 let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1468
1469 assert_eq!(modules, vec![]);
1470 }
1471
1472 #[test]
crate_to_bp_minimal()1473 fn crate_to_bp_minimal() {
1474 let c = Crate {
1475 name: "name".to_string(),
1476 package_name: "package_name".to_string(),
1477 edition: "2021".to_string(),
1478 types: vec![CrateType::Lib],
1479 ..Default::default()
1480 };
1481 let cfg = VariantConfig { ..Default::default() };
1482 let package_cfg = PackageVariantConfig { ..Default::default() };
1483 let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1484
1485 assert_eq!(
1486 modules,
1487 vec![BpModule {
1488 module_type: "rust_library".to_string(),
1489 props: BpProperties {
1490 map: [
1491 (
1492 "apex_available".to_string(),
1493 BpValue::List(vec![
1494 BpValue::String("//apex_available:platform".to_string()),
1495 BpValue::String("//apex_available:anyapex".to_string()),
1496 ])
1497 ),
1498 ("cargo_env_compat".to_string(), BpValue::Bool(true)),
1499 ("crate_name".to_string(), BpValue::String("name".to_string())),
1500 ("edition".to_string(), BpValue::String("2021".to_string())),
1501 ("host_supported".to_string(), BpValue::Bool(true)),
1502 ("name".to_string(), BpValue::String("libname".to_string())),
1503 ("product_available".to_string(), BpValue::Bool(true)),
1504 ("crate_root".to_string(), BpValue::String("".to_string())),
1505 ("vendor_available".to_string(), BpValue::Bool(true)),
1506 ]
1507 .into_iter()
1508 .collect(),
1509 raw_block: None
1510 }
1511 }]
1512 );
1513 }
1514
1515 #[test]
crate_to_bp_rename()1516 fn crate_to_bp_rename() {
1517 let c = Crate {
1518 name: "ash".to_string(),
1519 package_name: "package_name".to_string(),
1520 edition: "2021".to_string(),
1521 types: vec![CrateType::Lib],
1522 ..Default::default()
1523 };
1524 let cfg = VariantConfig { ..Default::default() };
1525 let package_cfg = PackageVariantConfig { ..Default::default() };
1526 let modules = crate_to_bp_modules(&c, &cfg, &package_cfg, &[]).unwrap();
1527
1528 assert_eq!(
1529 modules,
1530 vec![BpModule {
1531 module_type: "rust_library".to_string(),
1532 props: BpProperties {
1533 map: [
1534 (
1535 "apex_available".to_string(),
1536 BpValue::List(vec![
1537 BpValue::String("//apex_available:platform".to_string()),
1538 BpValue::String("//apex_available:anyapex".to_string()),
1539 ])
1540 ),
1541 ("cargo_env_compat".to_string(), BpValue::Bool(true)),
1542 ("crate_name".to_string(), BpValue::String("ash".to_string())),
1543 ("edition".to_string(), BpValue::String("2021".to_string())),
1544 ("host_supported".to_string(), BpValue::Bool(true)),
1545 ("name".to_string(), BpValue::String("libash_rust".to_string())),
1546 ("product_available".to_string(), BpValue::Bool(true)),
1547 ("crate_root".to_string(), BpValue::String("".to_string())),
1548 ("vendor_available".to_string(), BpValue::Bool(true)),
1549 ]
1550 .into_iter()
1551 .collect(),
1552 raw_block: None
1553 }
1554 }]
1555 );
1556 }
1557
1558 /// Returns a list of directories containing test data.
1559 ///
1560 /// Each directory under `testdata/` contains a single test case.
testdata_directories() -> Vec<PathBuf>1561 pub fn testdata_directories() -> Vec<PathBuf> {
1562 fs::read_dir(TESTDATA_PATH)
1563 .expect("Failed to read testdata directory")
1564 .filter_map(|entry| {
1565 let entry = entry.expect("Error reading testdata directory entry");
1566 if entry
1567 .file_type()
1568 .expect("Error getting metadata for testdata subdirectory")
1569 .is_dir()
1570 {
1571 Some(entry.path())
1572 } else {
1573 None
1574 }
1575 })
1576 .collect()
1577 }
1578 }
1579