xref: /aosp_15_r20/tools/asuite/adevice/src/tracking.rs (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
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