1 // Copyright (C) 2023 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 use std::{
16     collections::BTreeMap,
17     env,
18     path::Path,
19     process::{Command, Output},
20     str::from_utf8,
21     sync::mpsc::channel,
22 };
23 
24 use anyhow::{anyhow, Context, Result};
25 use threadpool::ThreadPool;
26 
27 use crate::{Crate, NameAndVersion, NameAndVersionMap, NamedAndVersioned, RepoPath};
28 
generate_android_bps<'a, T: Iterator<Item = &'a Crate>>( crates: T, ) -> Result<BTreeMap<NameAndVersion, Output>>29 pub fn generate_android_bps<'a, T: Iterator<Item = &'a Crate>>(
30     crates: T,
31 ) -> Result<BTreeMap<NameAndVersion, Output>> {
32     let pool = ThreadPool::new(std::cmp::max(num_cpus::get(), 32));
33     let (tx, rx) = channel();
34 
35     let mut num_crates = 0;
36     for krate in crates {
37         num_crates += 1;
38         let tx = tx.clone();
39         let crate_name = krate.name().to_string();
40         let crate_version = krate.version().clone();
41         let staging_path = krate.staging_path();
42         pool.execute(move || {
43             tx.send((crate_name, crate_version, generate_android_bp(&staging_path)))
44                 .expect("Failed to send");
45         });
46     }
47     let mut results = BTreeMap::new();
48     for (crate_name, crate_version, result) in rx.iter().take(num_crates) {
49         results.insert_or_error(NameAndVersion::new(crate_name, crate_version), result?)?;
50     }
51     Ok(results)
52 }
53 
generate_android_bp(staging_path: &RepoPath) -> Result<Output>54 pub(crate) fn generate_android_bp(staging_path: &RepoPath) -> Result<Output> {
55     let generate_android_bp_output = run_cargo_embargo(staging_path)?;
56     if !generate_android_bp_output.status.success() {
57         println!(
58             "cargo_embargo failed for {}\nstdout:\n{}\nstderr:\n{}",
59             staging_path,
60             from_utf8(&generate_android_bp_output.stdout)?,
61             from_utf8(&generate_android_bp_output.stderr)?
62         );
63     }
64     Ok(generate_android_bp_output)
65 }
66 
run_cargo_embargo(staging_path: &RepoPath) -> Result<Output>67 fn run_cargo_embargo(staging_path: &RepoPath) -> Result<Output> {
68     // Make sure we can find bpfmt.
69     let host_bin = staging_path.with_same_root(&"out/host/linux-x86/bin").abs();
70     let new_path = match env::var_os("PATH") {
71         Some(p) => {
72             let mut paths = vec![host_bin];
73             paths.extend(env::split_paths(&p));
74             env::join_paths(paths)?
75         }
76         None => host_bin.as_os_str().into(),
77     };
78 
79     let mut cmd =
80         Command::new(staging_path.with_same_root(&"out/host/linux-x86/bin/cargo_embargo").abs());
81     cmd.args(["generate", "cargo_embargo.json"])
82         .env("PATH", new_path)
83         .env("ANDROID_BUILD_TOP", staging_path.root())
84         .current_dir(staging_path.abs())
85         .output()
86         .context(format!("Failed to execute {:?}", cmd.get_program()))
87 }
88 
maybe_build_cargo_embargo(repo_root: &impl AsRef<Path>, force_rebuild: bool) -> Result<()>89 pub fn maybe_build_cargo_embargo(repo_root: &impl AsRef<Path>, force_rebuild: bool) -> Result<()> {
90     if !force_rebuild
91         && repo_root.as_ref().join("out/host/linux-x86/bin/cargo_embargo").exists()
92         && repo_root.as_ref().join("out/host/linux-x86/bin/bpfmt").exists()
93     {
94         Ok(())
95     } else {
96         println!("Rebuilding cargo_embargo");
97         build_cargo_embargo(repo_root)
98     }
99 }
100 
build_cargo_embargo(repo_root: &impl AsRef<Path>) -> Result<()>101 pub fn build_cargo_embargo(repo_root: &impl AsRef<Path>) -> Result<()> {
102     let status = Command::new("/usr/bin/bash")
103         .args(["-c", "source build/envsetup.sh && lunch aosp_cf_x86_64_phone-trunk_staging-eng && m cargo_embargo bpfmt"])
104         .current_dir(repo_root).spawn().context("Failed to spawn build of cargo embargo and bpfmt")?.wait().context("Failed to wait on child process building cargo embargo and bpfmt")?;
105     match status.success() {
106         true => Ok(()),
107         false => Err(anyhow!(
108             "Building cargo embargo and bpfmt failed with exit code {}",
109             status.code().map(|code| { format!("{}", code) }).unwrap_or("(unknown)".to_string())
110         )),
111     }
112 }
113