1*c2e18aaaSAndroid Build Coastguard Worker /// Module to keep track of which files should be pushed to a device.
2*c2e18aaaSAndroid Build Coastguard Worker /// Composed of:
3*c2e18aaaSAndroid Build Coastguard Worker /// 1) A tracking config that lets user specify modules to
4*c2e18aaaSAndroid Build Coastguard Worker /// augment a base image (droid).
5*c2e18aaaSAndroid Build Coastguard Worker /// 2) Integration with ninja to derive "installed" files from
6*c2e18aaaSAndroid Build Coastguard Worker /// this module set.
7*c2e18aaaSAndroid Build Coastguard Worker use anyhow::{bail, Context, Result};
8*c2e18aaaSAndroid Build Coastguard Worker use regex::Regex;
9*c2e18aaaSAndroid Build Coastguard Worker use serde::{Deserialize, Serialize};
10*c2e18aaaSAndroid Build Coastguard Worker use std::fs;
11*c2e18aaaSAndroid Build Coastguard Worker use std::io::BufReader;
12*c2e18aaaSAndroid Build Coastguard Worker use std::path::PathBuf;
13*c2e18aaaSAndroid Build Coastguard Worker use std::process;
14*c2e18aaaSAndroid Build Coastguard Worker use std::sync::LazyLock;
15*c2e18aaaSAndroid Build Coastguard Worker use tracing::{debug, warn};
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard Worker #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18*c2e18aaaSAndroid Build Coastguard Worker pub struct Config {
19*c2e18aaaSAndroid Build Coastguard Worker pub base: String,
20*c2e18aaaSAndroid Build Coastguard Worker pub modules: Vec<String>,
21*c2e18aaaSAndroid Build Coastguard Worker #[serde(default, skip_serializing, skip_deserializing)]
22*c2e18aaaSAndroid Build Coastguard Worker config_path: String,
23*c2e18aaaSAndroid Build Coastguard Worker }
24*c2e18aaaSAndroid Build Coastguard Worker
25*c2e18aaaSAndroid Build Coastguard Worker /// Object representing the files that are _tracked_. These are files that the
26*c2e18aaaSAndroid Build Coastguard Worker /// build system indicates should be on the device. Sometimes stale files
27*c2e18aaaSAndroid Build Coastguard Worker /// get left in the Product Out tree or extra modules get built into the Product Out tree.
28*c2e18aaaSAndroid Build Coastguard Worker /// This tracking config helps us call ninja to distinguish declared depdencies for
29*c2e18aaaSAndroid Build Coastguard Worker /// `droid` and what has been built.
30*c2e18aaaSAndroid Build Coastguard Worker /// TODO(rbraunstein): Rewrite above clearer.
31*c2e18aaaSAndroid Build Coastguard Worker impl Config {
32*c2e18aaaSAndroid Build Coastguard Worker /// Load set of tracked modules from User's homedir or return a default one.
33*c2e18aaaSAndroid Build Coastguard Worker /// If the user passes a config path, use it. Otherwise use the
34*c2e18aaaSAndroid Build Coastguard Worker /// default path in their home dir.
load(config_path: &Option<String>) -> Result<Self>35*c2e18aaaSAndroid Build Coastguard Worker pub fn load(config_path: &Option<String>) -> Result<Self> {
36*c2e18aaaSAndroid Build Coastguard Worker match &config_path {
37*c2e18aaaSAndroid Build Coastguard Worker Some(path) => Self::from_json_file(path),
38*c2e18aaaSAndroid Build Coastguard Worker None => match std::env::var("HOME") {
39*c2e18aaaSAndroid Build Coastguard Worker Ok(home) if !home.is_empty() => Self::load(&Some(Self::default_path(&home)?)),
40*c2e18aaaSAndroid Build Coastguard Worker _ => Ok(Self::default()),
41*c2e18aaaSAndroid Build Coastguard Worker },
42*c2e18aaaSAndroid Build Coastguard Worker }
43*c2e18aaaSAndroid Build Coastguard Worker }
44*c2e18aaaSAndroid Build Coastguard Worker
45*c2e18aaaSAndroid Build Coastguard Worker /// Load set of tracked modules from the given path or return a default one.
from_json_file(path: &String) -> Result<Self>46*c2e18aaaSAndroid Build Coastguard Worker fn from_json_file(path: &String) -> Result<Self> {
47*c2e18aaaSAndroid Build Coastguard Worker if let Ok(file) = fs::File::open(path) {
48*c2e18aaaSAndroid Build Coastguard Worker let mut config: Config = serde_json::from_reader(BufReader::new(file))
49*c2e18aaaSAndroid Build Coastguard Worker .context(format!("Parsing config {path:?}"))?;
50*c2e18aaaSAndroid Build Coastguard Worker config.config_path.clone_from(path);
51*c2e18aaaSAndroid Build Coastguard Worker return Ok(config);
52*c2e18aaaSAndroid Build Coastguard Worker }
53*c2e18aaaSAndroid Build Coastguard Worker // Lets not create a default config file until they actually track a module.
54*c2e18aaaSAndroid Build Coastguard Worker Ok(Config { base: "droid".to_string(), modules: Vec::new(), config_path: path.clone() })
55*c2e18aaaSAndroid Build Coastguard Worker }
56*c2e18aaaSAndroid Build Coastguard Worker
default() -> Self57*c2e18aaaSAndroid Build Coastguard Worker fn default() -> Self {
58*c2e18aaaSAndroid Build Coastguard Worker Config { base: "droid".to_string(), modules: Vec::new(), config_path: String::new() }
59*c2e18aaaSAndroid Build Coastguard Worker }
60*c2e18aaaSAndroid Build Coastguard Worker
print(&self)61*c2e18aaaSAndroid Build Coastguard Worker pub fn print(&self) {
62*c2e18aaaSAndroid Build Coastguard Worker debug!("Tracking base: `{}` and modules {:?}", self.base, self.modules);
63*c2e18aaaSAndroid Build Coastguard Worker }
64*c2e18aaaSAndroid Build Coastguard Worker
65*c2e18aaaSAndroid Build Coastguard Worker /// Returns the full path to the serialized config file.
default_path(home: &str) -> Result<String>66*c2e18aaaSAndroid Build Coastguard Worker fn default_path(home: &str) -> Result<String> {
67*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(format!("{home}/.config/asuite"))?;
68*c2e18aaaSAndroid Build Coastguard Worker Ok(format!("{home}/.config/asuite/adevice-tracking.json"))
69*c2e18aaaSAndroid Build Coastguard Worker }
70*c2e18aaaSAndroid Build Coastguard Worker
71*c2e18aaaSAndroid Build Coastguard Worker /// Adds the module name to the config and saves it.
track(&mut self, module_names: &[String]) -> Result<()>72*c2e18aaaSAndroid Build Coastguard Worker pub fn track(&mut self, module_names: &[String]) -> Result<()> {
73*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Validate the module names and warn on bad names.
74*c2e18aaaSAndroid Build Coastguard Worker self.modules.extend_from_slice(module_names);
75*c2e18aaaSAndroid Build Coastguard Worker self.modules.sort();
76*c2e18aaaSAndroid Build Coastguard Worker self.modules.dedup();
77*c2e18aaaSAndroid Build Coastguard Worker self.print();
78*c2e18aaaSAndroid Build Coastguard Worker self.clear_cache();
79*c2e18aaaSAndroid Build Coastguard Worker Self::save(self)
80*c2e18aaaSAndroid Build Coastguard Worker }
81*c2e18aaaSAndroid Build Coastguard Worker
82*c2e18aaaSAndroid Build Coastguard Worker /// Update the base module and saves it.
trackbase(&mut self, base: &str) -> Result<()>83*c2e18aaaSAndroid Build Coastguard Worker pub fn trackbase(&mut self, base: &str) -> Result<()> {
84*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Validate the module names and warn on bad names.
85*c2e18aaaSAndroid Build Coastguard Worker self.base = base.to_string();
86*c2e18aaaSAndroid Build Coastguard Worker self.print();
87*c2e18aaaSAndroid Build Coastguard Worker self.clear_cache();
88*c2e18aaaSAndroid Build Coastguard Worker Self::save(self)
89*c2e18aaaSAndroid Build Coastguard Worker }
90*c2e18aaaSAndroid Build Coastguard Worker
91*c2e18aaaSAndroid Build Coastguard Worker /// Removes the module name from the config and saves it.
untrack(&mut self, module_names: &[String]) -> Result<()>92*c2e18aaaSAndroid Build Coastguard Worker pub fn untrack(&mut self, module_names: &[String]) -> Result<()> {
93*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Report if not found?
94*c2e18aaaSAndroid Build Coastguard Worker self.modules.retain(|m| !module_names.contains(m));
95*c2e18aaaSAndroid Build Coastguard Worker self.print();
96*c2e18aaaSAndroid Build Coastguard Worker self.clear_cache();
97*c2e18aaaSAndroid Build Coastguard Worker Self::save(self)
98*c2e18aaaSAndroid Build Coastguard Worker }
99*c2e18aaaSAndroid Build Coastguard Worker
100*c2e18aaaSAndroid Build Coastguard Worker // Store the config as json at the config_path.
save(&self) -> Result<()>101*c2e18aaaSAndroid Build Coastguard Worker fn save(&self) -> Result<()> {
102*c2e18aaaSAndroid Build Coastguard Worker if self.config_path.is_empty() {
103*c2e18aaaSAndroid Build Coastguard Worker bail!("Can not save config file when HOME is not set and --config not set.")
104*c2e18aaaSAndroid Build Coastguard Worker }
105*c2e18aaaSAndroid Build Coastguard Worker let mut file = fs::File::create(&self.config_path)
106*c2e18aaaSAndroid Build Coastguard Worker .context(format!("Creating config file {:?}", self.config_path))?;
107*c2e18aaaSAndroid Build Coastguard Worker serde_json::to_writer_pretty(&mut file, &self).context("Writing config file")?;
108*c2e18aaaSAndroid Build Coastguard Worker debug!("Wrote config file {:?}", &self.config_path);
109*c2e18aaaSAndroid Build Coastguard Worker Ok(())
110*c2e18aaaSAndroid Build Coastguard Worker }
111*c2e18aaaSAndroid Build Coastguard Worker
112*c2e18aaaSAndroid Build Coastguard Worker /// Return all files that are part of the tracked set under ANDROID_PRODUCT_OUT.
113*c2e18aaaSAndroid Build Coastguard Worker /// Implementation:
114*c2e18aaaSAndroid Build Coastguard Worker /// Runs `ninja` to get all transitive intermediate targets for `droid`.
115*c2e18aaaSAndroid Build Coastguard Worker /// These intermediate targets contain all the apks and .sos, etc that
116*c2e18aaaSAndroid Build Coastguard Worker /// that get packaged for flashing.
117*c2e18aaaSAndroid Build Coastguard Worker /// Filter all the inputs returned by ninja to just those under
118*c2e18aaaSAndroid Build Coastguard Worker /// ANDROID_PRODUCT_OUT and explicitly ask for modules in our tracking set.
119*c2e18aaaSAndroid Build Coastguard Worker /// Extra or stale files in ANDROID_PRODUCT_OUT from builds will not be part
120*c2e18aaaSAndroid Build Coastguard Worker /// of the result.
121*c2e18aaaSAndroid Build Coastguard Worker /// The combined.ninja file will be found under:
122*c2e18aaaSAndroid Build Coastguard Worker /// ${ANDROID_BUILD_TOP}/${OUT_DIR}/combined-${TARGET_PRODUCT}.ninja
123*c2e18aaaSAndroid Build Coastguard Worker /// Tracked files inside that file are relative to $OUT_DIR/target/product/*/
124*c2e18aaaSAndroid Build Coastguard Worker /// The final element of the path can be derived from the final element of ANDROID_PRODUCT_OUT,
125*c2e18aaaSAndroid Build Coastguard Worker /// but matching against */target/product/* is enough.
126*c2e18aaaSAndroid Build Coastguard Worker /// Store all ninja deps in the cache.
tracked_files(&self) -> Result<Vec<String>>127*c2e18aaaSAndroid Build Coastguard Worker pub fn tracked_files(&self) -> Result<Vec<String>> {
128*c2e18aaaSAndroid Build Coastguard Worker if let Ok(cache) = self.read_cache() {
129*c2e18aaaSAndroid Build Coastguard Worker Ok(cache)
130*c2e18aaaSAndroid Build Coastguard Worker } else {
131*c2e18aaaSAndroid Build Coastguard Worker let ninja_output = self.ninja_output(
132*c2e18aaaSAndroid Build Coastguard Worker &self.src_root()?,
133*c2e18aaaSAndroid Build Coastguard Worker &self.ninja_args(&self.target_product()?, &self.out_dir()),
134*c2e18aaaSAndroid Build Coastguard Worker )?;
135*c2e18aaaSAndroid Build Coastguard Worker if !ninja_output.status.success() {
136*c2e18aaaSAndroid Build Coastguard Worker let stderr = String::from_utf8(ninja_output.stderr.clone()).unwrap();
137*c2e18aaaSAndroid Build Coastguard Worker anyhow::bail!("{}", self.ninja_failure_msg(&stderr));
138*c2e18aaaSAndroid Build Coastguard Worker }
139*c2e18aaaSAndroid Build Coastguard Worker let unfiltered_tracked_files = tracked_files(&ninja_output)?;
140*c2e18aaaSAndroid Build Coastguard Worker self.write_cache(&unfiltered_tracked_files)
141*c2e18aaaSAndroid Build Coastguard Worker .unwrap_or_else(|e| warn!("Error writing tracked file cache: {e}"));
142*c2e18aaaSAndroid Build Coastguard Worker Ok(unfiltered_tracked_files)
143*c2e18aaaSAndroid Build Coastguard Worker }
144*c2e18aaaSAndroid Build Coastguard Worker }
145*c2e18aaaSAndroid Build Coastguard Worker
src_root(&self) -> Result<String>146*c2e18aaaSAndroid Build Coastguard Worker pub fn src_root(&self) -> Result<String> {
147*c2e18aaaSAndroid Build Coastguard Worker std::env::var("ANDROID_BUILD_TOP")
148*c2e18aaaSAndroid Build Coastguard Worker .context("ANDROID_BUILD_TOP must be set. Be sure to run lunch.")
149*c2e18aaaSAndroid Build Coastguard Worker }
150*c2e18aaaSAndroid Build Coastguard Worker
target_product(&self) -> Result<String>151*c2e18aaaSAndroid Build Coastguard Worker fn target_product(&self) -> Result<String> {
152*c2e18aaaSAndroid Build Coastguard Worker std::env::var("TARGET_PRODUCT").context("TARGET_PRODUCT must be set. Be sure to run lunch.")
153*c2e18aaaSAndroid Build Coastguard Worker }
154*c2e18aaaSAndroid Build Coastguard Worker
out_dir(&self) -> String155*c2e18aaaSAndroid Build Coastguard Worker fn out_dir(&self) -> String {
156*c2e18aaaSAndroid Build Coastguard Worker std::env::var("OUT_DIR").unwrap_or("out".to_string())
157*c2e18aaaSAndroid Build Coastguard Worker }
158*c2e18aaaSAndroid Build Coastguard Worker
159*c2e18aaaSAndroid Build Coastguard Worker // Prepare the ninja command line args, creating the right ninja file name and
160*c2e18aaaSAndroid Build Coastguard Worker // appending all the modules.
ninja_args(&self, target_product: &str, out_dir: &str) -> Vec<String>161*c2e18aaaSAndroid Build Coastguard Worker fn ninja_args(&self, target_product: &str, out_dir: &str) -> Vec<String> {
162*c2e18aaaSAndroid Build Coastguard Worker // Create `ninja -f combined.ninja -t input -i BASE MOD1 MOD2 ....`
163*c2e18aaaSAndroid Build Coastguard Worker // The `-i` for intermediary is what gives the PRODUCT_OUT files.
164*c2e18aaaSAndroid Build Coastguard Worker let mut args = vec![
165*c2e18aaaSAndroid Build Coastguard Worker "-f".to_string(),
166*c2e18aaaSAndroid Build Coastguard Worker format!("{out_dir}/combined-{target_product}.ninja"),
167*c2e18aaaSAndroid Build Coastguard Worker "-t".to_string(),
168*c2e18aaaSAndroid Build Coastguard Worker "inputs".to_string(),
169*c2e18aaaSAndroid Build Coastguard Worker "-i".to_string(),
170*c2e18aaaSAndroid Build Coastguard Worker self.base.clone(),
171*c2e18aaaSAndroid Build Coastguard Worker ];
172*c2e18aaaSAndroid Build Coastguard Worker for module in self.modules.clone() {
173*c2e18aaaSAndroid Build Coastguard Worker args.push(module);
174*c2e18aaaSAndroid Build Coastguard Worker }
175*c2e18aaaSAndroid Build Coastguard Worker args
176*c2e18aaaSAndroid Build Coastguard Worker }
177*c2e18aaaSAndroid Build Coastguard Worker
178*c2e18aaaSAndroid Build Coastguard Worker // Call ninja.
ninja_output(&self, src_root: &str, args: &[String]) -> Result<process::Output>179*c2e18aaaSAndroid Build Coastguard Worker fn ninja_output(&self, src_root: &str, args: &[String]) -> Result<process::Output> {
180*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Deal with non-linux-x86.
181*c2e18aaaSAndroid Build Coastguard Worker let path = "prebuilts/build-tools/linux-x86/bin/ninja";
182*c2e18aaaSAndroid Build Coastguard Worker debug!("Running {path} {args:?}");
183*c2e18aaaSAndroid Build Coastguard Worker process::Command::new(path)
184*c2e18aaaSAndroid Build Coastguard Worker .current_dir(src_root)
185*c2e18aaaSAndroid Build Coastguard Worker .args(args)
186*c2e18aaaSAndroid Build Coastguard Worker .output()
187*c2e18aaaSAndroid Build Coastguard Worker .context("Running ninja to get base files")
188*c2e18aaaSAndroid Build Coastguard Worker }
189*c2e18aaaSAndroid Build Coastguard Worker
190*c2e18aaaSAndroid Build Coastguard Worker /// Check to see if the output from running ninja mentions a module we are tracking.
191*c2e18aaaSAndroid Build Coastguard Worker /// If a user tracks a module, but then removes it from the codebase, they should be notified.
192*c2e18aaaSAndroid Build Coastguard Worker /// Return origina ninja error and possibly a statement suggesting they `untrack` a module.
ninja_failure_msg(&self, stderr: &str) -> String193*c2e18aaaSAndroid Build Coastguard Worker fn ninja_failure_msg(&self, stderr: &str) -> String {
194*c2e18aaaSAndroid Build Coastguard Worker // A stale tracked target will look something like this:
195*c2e18aaaSAndroid Build Coastguard Worker // unknown target 'SomeStaleModule'
196*c2e18aaaSAndroid Build Coastguard Worker let mut msg = String::new();
197*c2e18aaaSAndroid Build Coastguard Worker for tracked_module in &self.modules {
198*c2e18aaaSAndroid Build Coastguard Worker if stderr.contains(tracked_module) {
199*c2e18aaaSAndroid Build Coastguard Worker msg = format!("You may need to `adevice untrack {}`", tracked_module);
200*c2e18aaaSAndroid Build Coastguard Worker }
201*c2e18aaaSAndroid Build Coastguard Worker }
202*c2e18aaaSAndroid Build Coastguard Worker if stderr.contains(&self.base) {
203*c2e18aaaSAndroid Build Coastguard Worker msg = format!(
204*c2e18aaaSAndroid Build Coastguard Worker "You may need to `adevice track-base` something other than `{}`",
205*c2e18aaaSAndroid Build Coastguard Worker &self.base
206*c2e18aaaSAndroid Build Coastguard Worker );
207*c2e18aaaSAndroid Build Coastguard Worker }
208*c2e18aaaSAndroid Build Coastguard Worker format!("{}{}", stderr, msg)
209*c2e18aaaSAndroid Build Coastguard Worker }
210*c2e18aaaSAndroid Build Coastguard Worker
clear_cache(&self)211*c2e18aaaSAndroid Build Coastguard Worker pub fn clear_cache(&self) {
212*c2e18aaaSAndroid Build Coastguard Worker let path = self.cache_path();
213*c2e18aaaSAndroid Build Coastguard Worker if path.is_err() {
214*c2e18aaaSAndroid Build Coastguard Worker warn!("Error getting the cache path {:?}", path.err().unwrap());
215*c2e18aaaSAndroid Build Coastguard Worker return;
216*c2e18aaaSAndroid Build Coastguard Worker }
217*c2e18aaaSAndroid Build Coastguard Worker match std::fs::remove_file(path.unwrap()) {
218*c2e18aaaSAndroid Build Coastguard Worker Ok(_) => (),
219*c2e18aaaSAndroid Build Coastguard Worker Err(e) => {
220*c2e18aaaSAndroid Build Coastguard Worker // Probably the cache has already been cleared and we can't remove it again.
221*c2e18aaaSAndroid Build Coastguard Worker debug!("Error clearing the cache {e}");
222*c2e18aaaSAndroid Build Coastguard Worker }
223*c2e18aaaSAndroid Build Coastguard Worker }
224*c2e18aaaSAndroid Build Coastguard Worker }
225*c2e18aaaSAndroid Build Coastguard Worker
226*c2e18aaaSAndroid Build Coastguard Worker // If our cache (in the out_dir) is newer than the ninja file, then use it rather
227*c2e18aaaSAndroid Build Coastguard Worker // than rerun ninja. Saves about 2 secs.
228*c2e18aaaSAndroid Build Coastguard Worker // Returns Err if cache not found or if cache is stale.
229*c2e18aaaSAndroid Build Coastguard Worker // Otherwise returns the stdout from the ninja command.
230*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): I don't think the cache is effective. I think the combined
231*c2e18aaaSAndroid Build Coastguard Worker // ninja file gets touched after every `m`. Either use the subninja or just turn off caching.
read_cache(&self) -> Result<Vec<String>>232*c2e18aaaSAndroid Build Coastguard Worker fn read_cache(&self) -> Result<Vec<String>> {
233*c2e18aaaSAndroid Build Coastguard Worker let cache_path = self.cache_path()?;
234*c2e18aaaSAndroid Build Coastguard Worker let ninja_file_path = PathBuf::from(&self.src_root()?)
235*c2e18aaaSAndroid Build Coastguard Worker .join(self.out_dir())
236*c2e18aaaSAndroid Build Coastguard Worker .join(format!("combined-{}.ninja", self.target_product()?));
237*c2e18aaaSAndroid Build Coastguard Worker // cache file is too old.
238*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Need integration tests for this.
239*c2e18aaaSAndroid Build Coastguard Worker // Adding and removing tracked modules affects the cache too.
240*c2e18aaaSAndroid Build Coastguard Worker debug!("Reading cache {cache_path}");
241*c2e18aaaSAndroid Build Coastguard Worker let cache_time = fs::metadata(&cache_path)?.modified()?;
242*c2e18aaaSAndroid Build Coastguard Worker debug!("Reading ninja {ninja_file_path:?}");
243*c2e18aaaSAndroid Build Coastguard Worker let ninja_file_time = fs::metadata(ninja_file_path)?.modified()?;
244*c2e18aaaSAndroid Build Coastguard Worker if cache_time.lt(&ninja_file_time) {
245*c2e18aaaSAndroid Build Coastguard Worker debug!("Cache is too old: {cache_time:?}, ninja file time {ninja_file_time:?}");
246*c2e18aaaSAndroid Build Coastguard Worker anyhow::bail!("cache is stale");
247*c2e18aaaSAndroid Build Coastguard Worker }
248*c2e18aaaSAndroid Build Coastguard Worker debug!("Using ninja file cache");
249*c2e18aaaSAndroid Build Coastguard Worker Ok(fs::read_to_string(&cache_path)?.split('\n').map(|s| s.to_string()).collect())
250*c2e18aaaSAndroid Build Coastguard Worker }
251*c2e18aaaSAndroid Build Coastguard Worker
cache_path(&self) -> Result<String>252*c2e18aaaSAndroid Build Coastguard Worker fn cache_path(&self) -> Result<String> {
253*c2e18aaaSAndroid Build Coastguard Worker Ok([
254*c2e18aaaSAndroid Build Coastguard Worker self.src_root()?,
255*c2e18aaaSAndroid Build Coastguard Worker self.out_dir(),
256*c2e18aaaSAndroid Build Coastguard Worker format!("adevice-ninja-deps-{}.cache", self.target_product()?),
257*c2e18aaaSAndroid Build Coastguard Worker ]
258*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Fix OS separator.
259*c2e18aaaSAndroid Build Coastguard Worker .join("/"))
260*c2e18aaaSAndroid Build Coastguard Worker }
261*c2e18aaaSAndroid Build Coastguard Worker
262*c2e18aaaSAndroid Build Coastguard Worker // Unconditionally write the given byte stream to the cache file
263*c2e18aaaSAndroid Build Coastguard Worker // overwriting whatever is there.
write_cache(&self, data: &[String]) -> Result<()>264*c2e18aaaSAndroid Build Coastguard Worker fn write_cache(&self, data: &[String]) -> Result<()> {
265*c2e18aaaSAndroid Build Coastguard Worker let cache_path = self.cache_path()?;
266*c2e18aaaSAndroid Build Coastguard Worker debug!("Wrote cache file: {cache_path:?}");
267*c2e18aaaSAndroid Build Coastguard Worker fs::write(cache_path, data.join("\n"))?;
268*c2e18aaaSAndroid Build Coastguard Worker Ok(())
269*c2e18aaaSAndroid Build Coastguard Worker }
270*c2e18aaaSAndroid Build Coastguard Worker }
271*c2e18aaaSAndroid Build Coastguard Worker
272*c2e18aaaSAndroid Build Coastguard Worker /// Iterate through the `ninja -t input -i MOD...` output
273*c2e18aaaSAndroid Build Coastguard Worker /// to find files in the PRODUCT_OUT directory.
tracked_files(output: &process::Output) -> Result<Vec<String>>274*c2e18aaaSAndroid Build Coastguard Worker fn tracked_files(output: &process::Output) -> Result<Vec<String>> {
275*c2e18aaaSAndroid Build Coastguard Worker let stdout = &output.stdout;
276*c2e18aaaSAndroid Build Coastguard Worker let stderr = &output.stderr;
277*c2e18aaaSAndroid Build Coastguard Worker debug!("NINJA calculated deps: {}", stdout.len());
278*c2e18aaaSAndroid Build Coastguard Worker if output.status.code().unwrap() > 0 || !stderr.is_empty() {
279*c2e18aaaSAndroid Build Coastguard Worker warn!("code: {} {:?}", output.status, String::from_utf8(stderr.to_owned()));
280*c2e18aaaSAndroid Build Coastguard Worker }
281*c2e18aaaSAndroid Build Coastguard Worker Ok(String::from_utf8(stdout.to_owned())?
282*c2e18aaaSAndroid Build Coastguard Worker .lines()
283*c2e18aaaSAndroid Build Coastguard Worker .filter_map(|line| {
284*c2e18aaaSAndroid Build Coastguard Worker if let Some(device_path) = strip_product_prefix(line) {
285*c2e18aaaSAndroid Build Coastguard Worker return Some(device_path);
286*c2e18aaaSAndroid Build Coastguard Worker }
287*c2e18aaaSAndroid Build Coastguard Worker None
288*c2e18aaaSAndroid Build Coastguard Worker })
289*c2e18aaaSAndroid Build Coastguard Worker .collect())
290*c2e18aaaSAndroid Build Coastguard Worker }
291*c2e18aaaSAndroid Build Coastguard Worker
292*c2e18aaaSAndroid Build Coastguard Worker // The ninja output for the files we are interested in will look like this:
293*c2e18aaaSAndroid Build Coastguard Worker // % OUT_DIR=innie m nothing
294*c2e18aaaSAndroid Build Coastguard Worker // % (cd $ANDROID_BUILD_TOP;prebuilts/build-tools/linux-x86/bin/ninja -f innie/combined-aosp_cf_x86_64_phone.ninja -t inputs -i droid | grep innie/target/product/vsoc_x86_64/system) | grep apk | head
295*c2e18aaaSAndroid Build Coastguard Worker // innie/target/product/vsoc_x86_64/system/app/BasicDreams/BasicDreams.apk
296*c2e18aaaSAndroid Build Coastguard Worker // innie/target/product/vsoc_x86_64/system/app/BluetoothMidiService/BluetoothMidiService.apk
297*c2e18aaaSAndroid Build Coastguard Worker // innie/target/product/vsoc_x86_64/system/app/BookmarkProvider/BookmarkProvider.apk
298*c2e18aaaSAndroid Build Coastguard Worker // innie/target/product/vsoc_x86_64/system/app/CameraExtensionsProxy/CameraExtensionsProxy.apk
299*c2e18aaaSAndroid Build Coastguard Worker // Match any files with target/product as the second and third dir paths and capture
300*c2e18aaaSAndroid Build Coastguard Worker // everything from 5th path element to the end.
301*c2e18aaaSAndroid Build Coastguard Worker static NINJA_OUT_PATH_MATCHER: LazyLock<Regex> = LazyLock::new(|| {
302*c2e18aaaSAndroid Build Coastguard Worker Regex::new(r"^[^/]+/target/product/[^/]+/(.+)$").expect("regex does not compile")
303*c2e18aaaSAndroid Build Coastguard Worker });
304*c2e18aaaSAndroid Build Coastguard Worker
strip_product_prefix(path: &str) -> Option<String>305*c2e18aaaSAndroid Build Coastguard Worker fn strip_product_prefix(path: &str) -> Option<String> {
306*c2e18aaaSAndroid Build Coastguard Worker NINJA_OUT_PATH_MATCHER.captures(path).map(|x| x[1].to_string())
307*c2e18aaaSAndroid Build Coastguard Worker }
308*c2e18aaaSAndroid Build Coastguard Worker
309*c2e18aaaSAndroid Build Coastguard Worker #[cfg(test)]
310*c2e18aaaSAndroid Build Coastguard Worker mod tests {
311*c2e18aaaSAndroid Build Coastguard Worker use super::*;
312*c2e18aaaSAndroid Build Coastguard Worker use tempfile::TempDir;
313*c2e18aaaSAndroid Build Coastguard Worker
314*c2e18aaaSAndroid Build Coastguard Worker #[test]
load_creates_new_config_with_droid() -> Result<()>315*c2e18aaaSAndroid Build Coastguard Worker fn load_creates_new_config_with_droid() -> Result<()> {
316*c2e18aaaSAndroid Build Coastguard Worker let home_dir = TempDir::new()?;
317*c2e18aaaSAndroid Build Coastguard Worker let config_path = home_dir.path().join("config.json").display().to_string();
318*c2e18aaaSAndroid Build Coastguard Worker let config = Config::load(&Some(config_path));
319*c2e18aaaSAndroid Build Coastguard Worker assert_eq!("droid", config?.base);
320*c2e18aaaSAndroid Build Coastguard Worker Ok(())
321*c2e18aaaSAndroid Build Coastguard Worker }
322*c2e18aaaSAndroid Build Coastguard Worker
323*c2e18aaaSAndroid Build Coastguard Worker #[test]
track_updates_config_file() -> Result<()>324*c2e18aaaSAndroid Build Coastguard Worker fn track_updates_config_file() -> Result<()> {
325*c2e18aaaSAndroid Build Coastguard Worker let home_dir = TempDir::new()?;
326*c2e18aaaSAndroid Build Coastguard Worker let config_path = home_dir.path().join("config.json").display().to_string();
327*c2e18aaaSAndroid Build Coastguard Worker let mut config = Config::load(&Some(config_path.clone()))?;
328*c2e18aaaSAndroid Build Coastguard Worker config.track(&["supermod".to_string()])?;
329*c2e18aaaSAndroid Build Coastguard Worker config.track(&["another".to_string()])?;
330*c2e18aaaSAndroid Build Coastguard Worker // Updates in-memory version, which gets sorted and deduped.
331*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(vec!["another".to_string(), "supermod".to_string()], config.modules);
332*c2e18aaaSAndroid Build Coastguard Worker
333*c2e18aaaSAndroid Build Coastguard Worker // Check the disk version too.
334*c2e18aaaSAndroid Build Coastguard Worker let config2 = Config::load(&Some(config_path))?;
335*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(config, config2);
336*c2e18aaaSAndroid Build Coastguard Worker Ok(())
337*c2e18aaaSAndroid Build Coastguard Worker }
338*c2e18aaaSAndroid Build Coastguard Worker
339*c2e18aaaSAndroid Build Coastguard Worker #[test]
untrack_updates_config() -> Result<()>340*c2e18aaaSAndroid Build Coastguard Worker fn untrack_updates_config() -> Result<()> {
341*c2e18aaaSAndroid Build Coastguard Worker let home_dir = TempDir::new()?;
342*c2e18aaaSAndroid Build Coastguard Worker let config_path = Config::default_path(&path(&home_dir)).context("Writing config")?;
343*c2e18aaaSAndroid Build Coastguard Worker std::fs::write(
344*c2e18aaaSAndroid Build Coastguard Worker config_path.clone(),
345*c2e18aaaSAndroid Build Coastguard Worker r#"{"base": "droid", "modules": [ "mod_one", "mod_two" ]}"#,
346*c2e18aaaSAndroid Build Coastguard Worker )?;
347*c2e18aaaSAndroid Build Coastguard Worker let mut config = Config::load(&Some(config_path.clone())).context("LOAD")?;
348*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(2, config.modules.len());
349*c2e18aaaSAndroid Build Coastguard Worker // Updates in-memory version.
350*c2e18aaaSAndroid Build Coastguard Worker config.untrack(&["mod_two".to_string()]).context("UNTRACK")?;
351*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(vec!["mod_one"], config.modules);
352*c2e18aaaSAndroid Build Coastguard Worker // Updates on-disk version.
353*c2e18aaaSAndroid Build Coastguard Worker Ok(())
354*c2e18aaaSAndroid Build Coastguard Worker }
355*c2e18aaaSAndroid Build Coastguard Worker
356*c2e18aaaSAndroid Build Coastguard Worker #[test]
ninja_args_updated_based_on_config()357*c2e18aaaSAndroid Build Coastguard Worker fn ninja_args_updated_based_on_config() {
358*c2e18aaaSAndroid Build Coastguard Worker let config =
359*c2e18aaaSAndroid Build Coastguard Worker Config { base: s("DROID"), modules: vec![s("ADEVICE_FP")], config_path: s("") };
360*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
361*c2e18aaaSAndroid Build Coastguard Worker crate::commands::split_string(
362*c2e18aaaSAndroid Build Coastguard Worker "-f outdir/combined-lynx.ninja -t inputs -i DROID ADEVICE_FP"
363*c2e18aaaSAndroid Build Coastguard Worker ),
364*c2e18aaaSAndroid Build Coastguard Worker config.ninja_args("lynx", "outdir")
365*c2e18aaaSAndroid Build Coastguard Worker );
366*c2e18aaaSAndroid Build Coastguard Worker // Find the args passed to ninja
367*c2e18aaaSAndroid Build Coastguard Worker }
368*c2e18aaaSAndroid Build Coastguard Worker
369*c2e18aaaSAndroid Build Coastguard Worker #[test]
ninja_output_filtered_to_android_product_out() -> Result<()>370*c2e18aaaSAndroid Build Coastguard Worker fn ninja_output_filtered_to_android_product_out() -> Result<()> {
371*c2e18aaaSAndroid Build Coastguard Worker // Ensure only paths matching */target/product/ remain
372*c2e18aaaSAndroid Build Coastguard Worker let fake_out = vec![
373*c2e18aaaSAndroid Build Coastguard Worker // 2 good ones
374*c2e18aaaSAndroid Build Coastguard Worker "innie/target/product/vsoc_x86_64/system/app/BasicDreams/BasicDreams.apk\n",
375*c2e18aaaSAndroid Build Coastguard Worker "innie/target/product/vsoc_x86_64/system/app/BookmarkProvider/BookmarkProvider.apk\n",
376*c2e18aaaSAndroid Build Coastguard Worker // Target/product not at right position
377*c2e18aaaSAndroid Build Coastguard Worker "innie/nested/target/product/vsoc_x86_64/system/NOT_FOUND\n",
378*c2e18aaaSAndroid Build Coastguard Worker // Different partition
379*c2e18aaaSAndroid Build Coastguard Worker "innie/target/product/vsoc_x86_64/OTHER_PARTITION/app/BasicDreams/BasicDreams2.apk\n",
380*c2e18aaaSAndroid Build Coastguard Worker // Good again.
381*c2e18aaaSAndroid Build Coastguard Worker "innie/target/product/vsoc_x86_64/system_ext/ok_file\n",
382*c2e18aaaSAndroid Build Coastguard Worker ];
383*c2e18aaaSAndroid Build Coastguard Worker
384*c2e18aaaSAndroid Build Coastguard Worker let output = process::Command::new("echo")
385*c2e18aaaSAndroid Build Coastguard Worker .args(&fake_out)
386*c2e18aaaSAndroid Build Coastguard Worker .output()
387*c2e18aaaSAndroid Build Coastguard Worker .context("Running ECHO to generate output")?;
388*c2e18aaaSAndroid Build Coastguard Worker
389*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
390*c2e18aaaSAndroid Build Coastguard Worker vec![
391*c2e18aaaSAndroid Build Coastguard Worker "system/app/BasicDreams/BasicDreams.apk",
392*c2e18aaaSAndroid Build Coastguard Worker "system/app/BookmarkProvider/BookmarkProvider.apk",
393*c2e18aaaSAndroid Build Coastguard Worker "OTHER_PARTITION/app/BasicDreams/BasicDreams2.apk",
394*c2e18aaaSAndroid Build Coastguard Worker "system_ext/ok_file",
395*c2e18aaaSAndroid Build Coastguard Worker ],
396*c2e18aaaSAndroid Build Coastguard Worker tracked_files(&output)?
397*c2e18aaaSAndroid Build Coastguard Worker );
398*c2e18aaaSAndroid Build Coastguard Worker Ok(())
399*c2e18aaaSAndroid Build Coastguard Worker }
400*c2e18aaaSAndroid Build Coastguard Worker
401*c2e18aaaSAndroid Build Coastguard Worker #[test]
check_ninja_failure_msg_for_tracked_module()402*c2e18aaaSAndroid Build Coastguard Worker fn check_ninja_failure_msg_for_tracked_module() {
403*c2e18aaaSAndroid Build Coastguard Worker // User tracks 'fish', which isn't a real module.
404*c2e18aaaSAndroid Build Coastguard Worker let config = Config { base: s("DROID"), modules: vec![s("fish")], config_path: s("") };
405*c2e18aaaSAndroid Build Coastguard Worker let msg = config.ninja_failure_msg(" error: unknown target 'fish', did you mean 'sh'");
406*c2e18aaaSAndroid Build Coastguard Worker
407*c2e18aaaSAndroid Build Coastguard Worker assert!(msg.contains("adevice untrack fish"), "Actual: {msg}")
408*c2e18aaaSAndroid Build Coastguard Worker }
409*c2e18aaaSAndroid Build Coastguard Worker
410*c2e18aaaSAndroid Build Coastguard Worker #[test]
check_ninja_failure_msg_for_special_base()411*c2e18aaaSAndroid Build Coastguard Worker fn check_ninja_failure_msg_for_special_base() {
412*c2e18aaaSAndroid Build Coastguard Worker let config = Config { base: s("R2D2_DROID"), modules: Vec::new(), config_path: s("") };
413*c2e18aaaSAndroid Build Coastguard Worker let msg = config.ninja_failure_msg(" error: unknown target 'R2D2_DROID'");
414*c2e18aaaSAndroid Build Coastguard Worker
415*c2e18aaaSAndroid Build Coastguard Worker assert!(msg.contains("adevice track-base"), "Actual: {msg}")
416*c2e18aaaSAndroid Build Coastguard Worker }
417*c2e18aaaSAndroid Build Coastguard Worker
418*c2e18aaaSAndroid Build Coastguard Worker #[test]
check_ninja_failure_msg_unrelated()419*c2e18aaaSAndroid Build Coastguard Worker fn check_ninja_failure_msg_unrelated() {
420*c2e18aaaSAndroid Build Coastguard Worker // User tracks 'bait', which is a real module, but gets some other error message.
421*c2e18aaaSAndroid Build Coastguard Worker let config = Config { base: s("DROID"), modules: vec![s("bait")], config_path: s("") };
422*c2e18aaaSAndroid Build Coastguard Worker
423*c2e18aaaSAndroid Build Coastguard Worker // There should be no untrack command.
424*c2e18aaaSAndroid Build Coastguard Worker assert!(!config
425*c2e18aaaSAndroid Build Coastguard Worker .ninja_failure_msg(" error: unknown target 'fish', did you mean 'sh'")
426*c2e18aaaSAndroid Build Coastguard Worker .contains("untrack"))
427*c2e18aaaSAndroid Build Coastguard Worker }
428*c2e18aaaSAndroid Build Coastguard Worker
429*c2e18aaaSAndroid Build Coastguard Worker /*
430*c2e18aaaSAndroid Build Coastguard Worker // Ensure we match the whole path component, i.e. "sys" should not match system.
431*c2e18aaaSAndroid Build Coastguard Worker #[test]
432*c2e18aaaSAndroid Build Coastguard Worker fn test_partition_filtering_partition_name_matches_path_component() {
433*c2e18aaaSAndroid Build Coastguard Worker let ninja_deps = vec![
434*c2e18aaaSAndroid Build Coastguard Worker "system/file1".to_string(),
435*c2e18aaaSAndroid Build Coastguard Worker "system_ext/file2".to_string(),
436*c2e18aaaSAndroid Build Coastguard Worker "file3".to_string(),
437*c2e18aaaSAndroid Build Coastguard Worker "data/sys/file4".to_string(),
438*c2e18aaaSAndroid Build Coastguard Worker ];
439*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
440*c2e18aaaSAndroid Build Coastguard Worker Vec::<String>::new(),
441*c2e18aaaSAndroid Build Coastguard Worker crate::tracking::filter_partitions(&ninja_deps, &[PathBuf::from("sys")])
442*c2e18aaaSAndroid Build Coastguard Worker );
443*c2e18aaaSAndroid Build Coastguard Worker }*/
444*c2e18aaaSAndroid Build Coastguard Worker
445*c2e18aaaSAndroid Build Coastguard Worker // Convert TempDir to string we can use for fs::write/read.
path(dir: &TempDir) -> String446*c2e18aaaSAndroid Build Coastguard Worker fn path(dir: &TempDir) -> String {
447*c2e18aaaSAndroid Build Coastguard Worker dir.path().display().to_string()
448*c2e18aaaSAndroid Build Coastguard Worker }
449*c2e18aaaSAndroid Build Coastguard Worker
450*c2e18aaaSAndroid Build Coastguard Worker // Tired of typing to_string()
s(str: &str) -> String451*c2e18aaaSAndroid Build Coastguard Worker fn s(str: &str) -> String {
452*c2e18aaaSAndroid Build Coastguard Worker str.to_string()
453*c2e18aaaSAndroid Build Coastguard Worker }
454*c2e18aaaSAndroid Build Coastguard Worker }
455