1*c2e18aaaSAndroid Build Coastguard Worker use crate::cli;
2*c2e18aaaSAndroid Build Coastguard Worker use crate::commands;
3*c2e18aaaSAndroid Build Coastguard Worker use crate::device;
4*c2e18aaaSAndroid Build Coastguard Worker use crate::fingerprint;
5*c2e18aaaSAndroid Build Coastguard Worker use crate::metrics;
6*c2e18aaaSAndroid Build Coastguard Worker use crate::progress;
7*c2e18aaaSAndroid Build Coastguard Worker use crate::restart_chooser;
8*c2e18aaaSAndroid Build Coastguard Worker use crate::tracking::Config;
9*c2e18aaaSAndroid Build Coastguard Worker use anyhow::{anyhow, bail, Context, Result};
10*c2e18aaaSAndroid Build Coastguard Worker use fingerprint::{DiffMode, FileMetadata};
11*c2e18aaaSAndroid Build Coastguard Worker use itertools::Itertools;
12*c2e18aaaSAndroid Build Coastguard Worker use metrics::MetricSender;
13*c2e18aaaSAndroid Build Coastguard Worker use rayon::prelude::*;
14*c2e18aaaSAndroid Build Coastguard Worker use regex::Regex;
15*c2e18aaaSAndroid Build Coastguard Worker use restart_chooser::RestartChooser;
16*c2e18aaaSAndroid Build Coastguard Worker use tracing::{debug, Level};
17*c2e18aaaSAndroid Build Coastguard Worker
18*c2e18aaaSAndroid Build Coastguard Worker use std::collections::{HashMap, HashSet};
19*c2e18aaaSAndroid Build Coastguard Worker use std::ffi::OsString;
20*c2e18aaaSAndroid Build Coastguard Worker use std::fs;
21*c2e18aaaSAndroid Build Coastguard Worker use std::fs::File;
22*c2e18aaaSAndroid Build Coastguard Worker use std::io::{stdin, Write};
23*c2e18aaaSAndroid Build Coastguard Worker use std::path::{Path, PathBuf};
24*c2e18aaaSAndroid Build Coastguard Worker use std::sync::{LazyLock, Mutex};
25*c2e18aaaSAndroid Build Coastguard Worker use std::time::Duration;
26*c2e18aaaSAndroid Build Coastguard Worker
27*c2e18aaaSAndroid Build Coastguard Worker /// Methods that interact with the host, like fingerprinting and calling ninja to get deps.
28*c2e18aaaSAndroid Build Coastguard Worker pub trait Host {
29*c2e18aaaSAndroid Build Coastguard Worker /// Return all files in the given partitions at the partition_root along with metadata for those files.
30*c2e18aaaSAndroid Build Coastguard Worker /// The keys in the returned hashmap will be relative to partition_root.
fingerprint( &self, partition_root: &Path, partitions: &[PathBuf], ) -> Result<HashMap<PathBuf, FileMetadata>>31*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint(
32*c2e18aaaSAndroid Build Coastguard Worker &self,
33*c2e18aaaSAndroid Build Coastguard Worker partition_root: &Path,
34*c2e18aaaSAndroid Build Coastguard Worker partitions: &[PathBuf],
35*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<HashMap<PathBuf, FileMetadata>>;
36*c2e18aaaSAndroid Build Coastguard Worker
37*c2e18aaaSAndroid Build Coastguard Worker /// Return a list of all files that compose `droid` or whatever base and tracked
38*c2e18aaaSAndroid Build Coastguard Worker /// modules are listed in `config`.
39*c2e18aaaSAndroid Build Coastguard Worker /// Result strings are device relative. (i.e. start with system)
tracked_files(&self, config: &Config) -> Result<Vec<String>>40*c2e18aaaSAndroid Build Coastguard Worker fn tracked_files(&self, config: &Config) -> Result<Vec<String>>;
41*c2e18aaaSAndroid Build Coastguard Worker }
42*c2e18aaaSAndroid Build Coastguard Worker
43*c2e18aaaSAndroid Build Coastguard Worker /// Methods to interact with the device, like adb, rebooting, and fingerprinting.
44*c2e18aaaSAndroid Build Coastguard Worker pub trait Device {
45*c2e18aaaSAndroid Build Coastguard Worker /// Run the `commands` and return the stdout as a string. If there is non-zero return code
46*c2e18aaaSAndroid Build Coastguard Worker /// or output on stderr, then the result is an Err.
run_adb_command(&self, args: &commands::AdbCommand) -> Result<String>47*c2e18aaaSAndroid Build Coastguard Worker fn run_adb_command(&self, args: &commands::AdbCommand) -> Result<String>;
48*c2e18aaaSAndroid Build Coastguard Worker
run_raw_adb_command(&self, args: &[String]) -> Result<String>49*c2e18aaaSAndroid Build Coastguard Worker fn run_raw_adb_command(&self, args: &[String]) -> Result<String>;
50*c2e18aaaSAndroid Build Coastguard Worker
51*c2e18aaaSAndroid Build Coastguard Worker /// Send commands to reboot device.
reboot(&self) -> Result<String>52*c2e18aaaSAndroid Build Coastguard Worker fn reboot(&self) -> Result<String>;
53*c2e18aaaSAndroid Build Coastguard Worker /// Send commands to do a soft restart.
soft_restart(&self) -> Result<String>54*c2e18aaaSAndroid Build Coastguard Worker fn soft_restart(&self) -> Result<String>;
55*c2e18aaaSAndroid Build Coastguard Worker
56*c2e18aaaSAndroid Build Coastguard Worker /// Call the fingerprint program on the device.
fingerprint(&self, partitions: &[String]) -> Result<HashMap<PathBuf, FileMetadata>>57*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint(&self, partitions: &[String]) -> Result<HashMap<PathBuf, FileMetadata>>;
58*c2e18aaaSAndroid Build Coastguard Worker
59*c2e18aaaSAndroid Build Coastguard Worker /// Return the list apks that are currently installed, i.e. `adb install`
60*c2e18aaaSAndroid Build Coastguard Worker /// which live on the /data partition.
61*c2e18aaaSAndroid Build Coastguard Worker /// Returns the package name, i.e. "com.android.shell".
get_installed_apks(&self) -> Result<HashSet<String>>62*c2e18aaaSAndroid Build Coastguard Worker fn get_installed_apks(&self) -> Result<HashSet<String>>;
63*c2e18aaaSAndroid Build Coastguard Worker
64*c2e18aaaSAndroid Build Coastguard Worker /// Wait for the device to be ready after reboots/restarts.
65*c2e18aaaSAndroid Build Coastguard Worker /// Returns any relevant output from waiting.
wait(&self, profiler: &mut Profiler) -> Result<String>66*c2e18aaaSAndroid Build Coastguard Worker fn wait(&self, profiler: &mut Profiler) -> Result<String>;
67*c2e18aaaSAndroid Build Coastguard Worker
68*c2e18aaaSAndroid Build Coastguard Worker /// Run the commands needed to prep a userdebug device after a flash.
prep_after_flash(&self, profiler: &mut Profiler) -> Result<()>69*c2e18aaaSAndroid Build Coastguard Worker fn prep_after_flash(&self, profiler: &mut Profiler) -> Result<()>;
70*c2e18aaaSAndroid Build Coastguard Worker }
71*c2e18aaaSAndroid Build Coastguard Worker
72*c2e18aaaSAndroid Build Coastguard Worker pub struct RealHost {}
73*c2e18aaaSAndroid Build Coastguard Worker
74*c2e18aaaSAndroid Build Coastguard Worker impl Default for RealHost {
default() -> Self75*c2e18aaaSAndroid Build Coastguard Worker fn default() -> Self {
76*c2e18aaaSAndroid Build Coastguard Worker Self::new()
77*c2e18aaaSAndroid Build Coastguard Worker }
78*c2e18aaaSAndroid Build Coastguard Worker }
79*c2e18aaaSAndroid Build Coastguard Worker
80*c2e18aaaSAndroid Build Coastguard Worker impl RealHost {
new() -> RealHost81*c2e18aaaSAndroid Build Coastguard Worker pub fn new() -> RealHost {
82*c2e18aaaSAndroid Build Coastguard Worker RealHost {}
83*c2e18aaaSAndroid Build Coastguard Worker }
84*c2e18aaaSAndroid Build Coastguard Worker }
85*c2e18aaaSAndroid Build Coastguard Worker
86*c2e18aaaSAndroid Build Coastguard Worker impl Host for RealHost {
fingerprint( &self, partition_root: &Path, partitions: &[PathBuf], ) -> Result<HashMap<PathBuf, FileMetadata>>87*c2e18aaaSAndroid Build Coastguard Worker fn fingerprint(
88*c2e18aaaSAndroid Build Coastguard Worker &self,
89*c2e18aaaSAndroid Build Coastguard Worker partition_root: &Path,
90*c2e18aaaSAndroid Build Coastguard Worker partitions: &[PathBuf],
91*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<HashMap<PathBuf, FileMetadata>> {
92*c2e18aaaSAndroid Build Coastguard Worker fingerprint::fingerprint_partitions(partition_root, partitions)
93*c2e18aaaSAndroid Build Coastguard Worker }
94*c2e18aaaSAndroid Build Coastguard Worker
tracked_files(&self, config: &Config) -> Result<Vec<String>>95*c2e18aaaSAndroid Build Coastguard Worker fn tracked_files(&self, config: &Config) -> Result<Vec<String>> {
96*c2e18aaaSAndroid Build Coastguard Worker config.tracked_files()
97*c2e18aaaSAndroid Build Coastguard Worker }
98*c2e18aaaSAndroid Build Coastguard Worker }
99*c2e18aaaSAndroid Build Coastguard Worker
100*c2e18aaaSAndroid Build Coastguard Worker /// Time how long it takes to run the function and store the
101*c2e18aaaSAndroid Build Coastguard Worker /// result in the given profiler field.
102*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Ideally, use tracing or flamegraph crate or
103*c2e18aaaSAndroid Build Coastguard Worker // use Map rather than name all the fields.
104*c2e18aaaSAndroid Build Coastguard Worker // See: https://docs.rs/tracing/latest/tracing/index.html#using-the-macros and span!
105*c2e18aaaSAndroid Build Coastguard Worker #[macro_export]
106*c2e18aaaSAndroid Build Coastguard Worker macro_rules! time {
107*c2e18aaaSAndroid Build Coastguard Worker ($fn:expr, $ident:expr) => {{
108*c2e18aaaSAndroid Build Coastguard Worker let start = std::time::Instant::now();
109*c2e18aaaSAndroid Build Coastguard Worker let result = $fn;
110*c2e18aaaSAndroid Build Coastguard Worker $ident = start.elapsed();
111*c2e18aaaSAndroid Build Coastguard Worker result
112*c2e18aaaSAndroid Build Coastguard Worker }};
113*c2e18aaaSAndroid Build Coastguard Worker }
114*c2e18aaaSAndroid Build Coastguard Worker
adevice( host: &impl Host, device: &impl Device, cli: &cli::Cli, stdout: &mut impl Write, metrics: &mut impl MetricSender, opt_log_file: Option<File>, profiler: &mut Profiler, ) -> Result<()>115*c2e18aaaSAndroid Build Coastguard Worker pub fn adevice(
116*c2e18aaaSAndroid Build Coastguard Worker host: &impl Host,
117*c2e18aaaSAndroid Build Coastguard Worker device: &impl Device,
118*c2e18aaaSAndroid Build Coastguard Worker cli: &cli::Cli,
119*c2e18aaaSAndroid Build Coastguard Worker stdout: &mut impl Write,
120*c2e18aaaSAndroid Build Coastguard Worker metrics: &mut impl MetricSender,
121*c2e18aaaSAndroid Build Coastguard Worker opt_log_file: Option<File>,
122*c2e18aaaSAndroid Build Coastguard Worker profiler: &mut Profiler,
123*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<()> {
124*c2e18aaaSAndroid Build Coastguard Worker // If we can initialize a log file, then setup the tracing/log subscriber to write there.
125*c2e18aaaSAndroid Build Coastguard Worker // Otherwise, logs will be dropped.
126*c2e18aaaSAndroid Build Coastguard Worker if let Some(log_file) = opt_log_file {
127*c2e18aaaSAndroid Build Coastguard Worker let subscriber = tracing_subscriber::fmt()
128*c2e18aaaSAndroid Build Coastguard Worker .with_max_level(Level::DEBUG)
129*c2e18aaaSAndroid Build Coastguard Worker .with_writer(Mutex::new(log_file))
130*c2e18aaaSAndroid Build Coastguard Worker .finish();
131*c2e18aaaSAndroid Build Coastguard Worker tracing::subscriber::set_global_default(subscriber)?;
132*c2e18aaaSAndroid Build Coastguard Worker }
133*c2e18aaaSAndroid Build Coastguard Worker
134*c2e18aaaSAndroid Build Coastguard Worker let restart_choice = cli.global_options.restart_choice.clone();
135*c2e18aaaSAndroid Build Coastguard Worker
136*c2e18aaaSAndroid Build Coastguard Worker let product_out = match &cli.global_options.product_out {
137*c2e18aaaSAndroid Build Coastguard Worker Some(po) => PathBuf::from(po),
138*c2e18aaaSAndroid Build Coastguard Worker None => get_product_out_from_env().ok_or(anyhow!(
139*c2e18aaaSAndroid Build Coastguard Worker "ANDROID_PRODUCT_OUT is not set. Please run source build/envsetup.sh and lunch."
140*c2e18aaaSAndroid Build Coastguard Worker ))?,
141*c2e18aaaSAndroid Build Coastguard Worker };
142*c2e18aaaSAndroid Build Coastguard Worker
143*c2e18aaaSAndroid Build Coastguard Worker let track_time = std::time::Instant::now();
144*c2e18aaaSAndroid Build Coastguard Worker
145*c2e18aaaSAndroid Build Coastguard Worker let mut config = Config::load(&cli.global_options.config_path)?;
146*c2e18aaaSAndroid Build Coastguard Worker
147*c2e18aaaSAndroid Build Coastguard Worker let command_line = std::env::args().collect::<Vec<String>>().join(" ");
148*c2e18aaaSAndroid Build Coastguard Worker metrics.add_start_event(&command_line, &config.src_root()?);
149*c2e18aaaSAndroid Build Coastguard Worker
150*c2e18aaaSAndroid Build Coastguard Worker // Early return for track/untrack commands.
151*c2e18aaaSAndroid Build Coastguard Worker match &cli.command {
152*c2e18aaaSAndroid Build Coastguard Worker cli::Commands::Track(names) => return config.track(&names.modules),
153*c2e18aaaSAndroid Build Coastguard Worker cli::Commands::TrackBase(base) => return config.trackbase(&base.base),
154*c2e18aaaSAndroid Build Coastguard Worker cli::Commands::Untrack(names) => return config.untrack(&names.modules),
155*c2e18aaaSAndroid Build Coastguard Worker _ => (),
156*c2e18aaaSAndroid Build Coastguard Worker }
157*c2e18aaaSAndroid Build Coastguard Worker config.print();
158*c2e18aaaSAndroid Build Coastguard Worker
159*c2e18aaaSAndroid Build Coastguard Worker writeln!(stdout, " * Checking for files to push to device")?;
160*c2e18aaaSAndroid Build Coastguard Worker
161*c2e18aaaSAndroid Build Coastguard Worker progress::start("Checking ninja installed files");
162*c2e18aaaSAndroid Build Coastguard Worker let mut ninja_installed_files =
163*c2e18aaaSAndroid Build Coastguard Worker time!(host.tracked_files(&config)?, profiler.ninja_deps_computer);
164*c2e18aaaSAndroid Build Coastguard Worker let partitions =
165*c2e18aaaSAndroid Build Coastguard Worker &validate_partitions(&product_out, &ninja_installed_files, &cli.global_options.partitions)?;
166*c2e18aaaSAndroid Build Coastguard Worker // Filter to paths on any partitions.
167*c2e18aaaSAndroid Build Coastguard Worker ninja_installed_files
168*c2e18aaaSAndroid Build Coastguard Worker .retain(|nif| partitions.iter().any(|p| PathBuf::from(nif).starts_with(p)));
169*c2e18aaaSAndroid Build Coastguard Worker debug!("Stale file tracking took {} millis", track_time.elapsed().as_millis());
170*c2e18aaaSAndroid Build Coastguard Worker progress::update("Checking files on device");
171*c2e18aaaSAndroid Build Coastguard Worker let mut device_tree: HashMap<PathBuf, FileMetadata> =
172*c2e18aaaSAndroid Build Coastguard Worker time!(device.fingerprint(partitions)?, profiler.device_fingerprint);
173*c2e18aaaSAndroid Build Coastguard Worker // We expect the device to create lost+found dirs when mounting
174*c2e18aaaSAndroid Build Coastguard Worker // new partitions. Filter them out as if they don't exist.
175*c2e18aaaSAndroid Build Coastguard Worker // However, if there are file inside of them, don't filter the
176*c2e18aaaSAndroid Build Coastguard Worker // inner files.
177*c2e18aaaSAndroid Build Coastguard Worker for p in partitions {
178*c2e18aaaSAndroid Build Coastguard Worker device_tree.remove(&PathBuf::from(p).join("lost+found"));
179*c2e18aaaSAndroid Build Coastguard Worker }
180*c2e18aaaSAndroid Build Coastguard Worker progress::update("Checking files on host");
181*c2e18aaaSAndroid Build Coastguard Worker let partition_paths: Vec<PathBuf> = partitions.iter().map(PathBuf::from).collect();
182*c2e18aaaSAndroid Build Coastguard Worker let host_tree =
183*c2e18aaaSAndroid Build Coastguard Worker time!(host.fingerprint(&product_out, &partition_paths)?, profiler.host_fingerprint);
184*c2e18aaaSAndroid Build Coastguard Worker progress::update("Calculating diffs");
185*c2e18aaaSAndroid Build Coastguard Worker // For now ignore diffs in permissions. This will allow us to have a new adevice host tool
186*c2e18aaaSAndroid Build Coastguard Worker // still working with an older adevice_fingerprint device tool.
187*c2e18aaaSAndroid Build Coastguard Worker // [It also works on windows hosts]
188*c2e18aaaSAndroid Build Coastguard Worker // Version 0.2 of the device tool will support permission mode.
189*c2e18aaaSAndroid Build Coastguard Worker // We can check for that version of the tool or check to see if the metadata
190*c2e18aaaSAndroid Build Coastguard Worker // on a well-known file (like system/bin/adevice_fingerprint) contains permission
191*c2e18aaaSAndroid Build Coastguard Worker // bits before we change this to UsePermissions.
192*c2e18aaaSAndroid Build Coastguard Worker let diff_mode = fingerprint::DiffMode::IgnorePermissions;
193*c2e18aaaSAndroid Build Coastguard Worker
194*c2e18aaaSAndroid Build Coastguard Worker let commands = &get_update_commands(
195*c2e18aaaSAndroid Build Coastguard Worker &device_tree,
196*c2e18aaaSAndroid Build Coastguard Worker &host_tree,
197*c2e18aaaSAndroid Build Coastguard Worker &ninja_installed_files,
198*c2e18aaaSAndroid Build Coastguard Worker product_out.clone(),
199*c2e18aaaSAndroid Build Coastguard Worker &device.get_installed_apks()?,
200*c2e18aaaSAndroid Build Coastguard Worker diff_mode,
201*c2e18aaaSAndroid Build Coastguard Worker &partition_paths,
202*c2e18aaaSAndroid Build Coastguard Worker cli.global_options.force,
203*c2e18aaaSAndroid Build Coastguard Worker stdout,
204*c2e18aaaSAndroid Build Coastguard Worker )?;
205*c2e18aaaSAndroid Build Coastguard Worker progress::stop();
206*c2e18aaaSAndroid Build Coastguard Worker #[allow(clippy::collapsible_if)]
207*c2e18aaaSAndroid Build Coastguard Worker if matches!(cli.command, cli::Commands::Status) {
208*c2e18aaaSAndroid Build Coastguard Worker if commands.is_empty() {
209*c2e18aaaSAndroid Build Coastguard Worker println!(" Device already up to date.");
210*c2e18aaaSAndroid Build Coastguard Worker }
211*c2e18aaaSAndroid Build Coastguard Worker }
212*c2e18aaaSAndroid Build Coastguard Worker
213*c2e18aaaSAndroid Build Coastguard Worker let max_changes = cli.global_options.max_allowed_changes;
214*c2e18aaaSAndroid Build Coastguard Worker if matches!(cli.command, cli::Commands::Clean { .. }) {
215*c2e18aaaSAndroid Build Coastguard Worker let deletes = &commands.deletes;
216*c2e18aaaSAndroid Build Coastguard Worker if deletes.is_empty() {
217*c2e18aaaSAndroid Build Coastguard Worker println!(" Nothing to clean.");
218*c2e18aaaSAndroid Build Coastguard Worker return Ok(());
219*c2e18aaaSAndroid Build Coastguard Worker }
220*c2e18aaaSAndroid Build Coastguard Worker if deletes.len() > max_changes {
221*c2e18aaaSAndroid Build Coastguard Worker bail!("There are {} files to be deleted which exceeds the configured limit of {}.\n It is recommended that you reimage your device instead. For small increases in the limit, you can run `adevice clean --max-allowed-changes={}.", deletes.len(), max_changes, deletes.len());
222*c2e18aaaSAndroid Build Coastguard Worker }
223*c2e18aaaSAndroid Build Coastguard Worker if matches!(cli.command, cli::Commands::Clean { force } if !force) {
224*c2e18aaaSAndroid Build Coastguard Worker println!(
225*c2e18aaaSAndroid Build Coastguard Worker "You are about to delete {} [untracked pushed] files. Are you sure? y/N",
226*c2e18aaaSAndroid Build Coastguard Worker deletes.len()
227*c2e18aaaSAndroid Build Coastguard Worker );
228*c2e18aaaSAndroid Build Coastguard Worker let mut should_delete = String::new();
229*c2e18aaaSAndroid Build Coastguard Worker stdin().read_line(&mut should_delete)?;
230*c2e18aaaSAndroid Build Coastguard Worker if should_delete.trim().to_lowercase() != "y" {
231*c2e18aaaSAndroid Build Coastguard Worker bail!("Not deleting");
232*c2e18aaaSAndroid Build Coastguard Worker }
233*c2e18aaaSAndroid Build Coastguard Worker }
234*c2e18aaaSAndroid Build Coastguard Worker
235*c2e18aaaSAndroid Build Coastguard Worker // Consider always reboot instead of soft restart after a clean.
236*c2e18aaaSAndroid Build Coastguard Worker let restart_chooser = &RestartChooser::new(&restart_choice);
237*c2e18aaaSAndroid Build Coastguard Worker device::update(restart_chooser, deletes, profiler, device, cli.should_wait())?;
238*c2e18aaaSAndroid Build Coastguard Worker }
239*c2e18aaaSAndroid Build Coastguard Worker
240*c2e18aaaSAndroid Build Coastguard Worker if matches!(cli.command, cli::Commands::Update) {
241*c2e18aaaSAndroid Build Coastguard Worker // Status
242*c2e18aaaSAndroid Build Coastguard Worker if commands.is_empty() {
243*c2e18aaaSAndroid Build Coastguard Worker println!(" Device already up to date.");
244*c2e18aaaSAndroid Build Coastguard Worker return Ok(());
245*c2e18aaaSAndroid Build Coastguard Worker }
246*c2e18aaaSAndroid Build Coastguard Worker let all_cmds: HashMap<PathBuf, commands::AdbCommand> =
247*c2e18aaaSAndroid Build Coastguard Worker commands.upserts.clone().into_iter().chain(commands.deletes.clone()).collect();
248*c2e18aaaSAndroid Build Coastguard Worker
249*c2e18aaaSAndroid Build Coastguard Worker if all_cmds.len() > max_changes {
250*c2e18aaaSAndroid Build Coastguard Worker bail!("There are {} files out of date on the device, which exceeds the configured limit of {}.\n It is recommended to reimage your device. For small increases in the limit, you can run `adevice update --max-allowed-changes={}.", all_cmds.len(), max_changes, all_cmds.len());
251*c2e18aaaSAndroid Build Coastguard Worker }
252*c2e18aaaSAndroid Build Coastguard Worker writeln!(stdout, "\n * Updating {} files on device.", all_cmds.len())?;
253*c2e18aaaSAndroid Build Coastguard Worker
254*c2e18aaaSAndroid Build Coastguard Worker let changed_files = all_cmds.iter().map(|cmd| format!("{:?}", cmd.1.file)).collect();
255*c2e18aaaSAndroid Build Coastguard Worker metrics.add_action_event_with_files_changed(
256*c2e18aaaSAndroid Build Coastguard Worker "file_updates",
257*c2e18aaaSAndroid Build Coastguard Worker Duration::new(0, 0),
258*c2e18aaaSAndroid Build Coastguard Worker changed_files,
259*c2e18aaaSAndroid Build Coastguard Worker );
260*c2e18aaaSAndroid Build Coastguard Worker
261*c2e18aaaSAndroid Build Coastguard Worker // Send the update commands, but retry once if we need to remount rw an extra time after a flash.
262*c2e18aaaSAndroid Build Coastguard Worker for retry in 0..=1 {
263*c2e18aaaSAndroid Build Coastguard Worker let update_result = device::update(
264*c2e18aaaSAndroid Build Coastguard Worker &RestartChooser::new(&restart_choice),
265*c2e18aaaSAndroid Build Coastguard Worker &all_cmds,
266*c2e18aaaSAndroid Build Coastguard Worker profiler,
267*c2e18aaaSAndroid Build Coastguard Worker device,
268*c2e18aaaSAndroid Build Coastguard Worker cli.should_wait(),
269*c2e18aaaSAndroid Build Coastguard Worker );
270*c2e18aaaSAndroid Build Coastguard Worker progress::stop();
271*c2e18aaaSAndroid Build Coastguard Worker if update_result.is_ok() {
272*c2e18aaaSAndroid Build Coastguard Worker break;
273*c2e18aaaSAndroid Build Coastguard Worker }
274*c2e18aaaSAndroid Build Coastguard Worker if let Err(problem) = update_result {
275*c2e18aaaSAndroid Build Coastguard Worker if retry == 1 {
276*c2e18aaaSAndroid Build Coastguard Worker println!("\n\n");
277*c2e18aaaSAndroid Build Coastguard Worker bail!(" !! Error. Unable to push to device event after remount/reboot.\n !! ADB command error: {:?}", problem);
278*c2e18aaaSAndroid Build Coastguard Worker }
279*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Avoid string checks. Either check mounts directly for this case
280*c2e18aaaSAndroid Build Coastguard Worker // or return json with the error message and code from adevice_fingerprint.
281*c2e18aaaSAndroid Build Coastguard Worker
282*c2e18aaaSAndroid Build Coastguard Worker if problem.root_cause().to_string().contains("Read-only file system") {
283*c2e18aaaSAndroid Build Coastguard Worker println!(" * The device has a read-only file system. ");
284*c2e18aaaSAndroid Build Coastguard Worker println!(" After a fresh image, the device needs an extra `remount` and `reboot` to adb push files.");
285*c2e18aaaSAndroid Build Coastguard Worker println!(" Performing remount and reboot.");
286*c2e18aaaSAndroid Build Coastguard Worker println!();
287*c2e18aaaSAndroid Build Coastguard Worker }
288*c2e18aaaSAndroid Build Coastguard Worker time!(device.prep_after_flash(profiler)?, profiler.first_remount_rw);
289*c2e18aaaSAndroid Build Coastguard Worker }
290*c2e18aaaSAndroid Build Coastguard Worker println!(" * Trying update again after remount and reboot.");
291*c2e18aaaSAndroid Build Coastguard Worker }
292*c2e18aaaSAndroid Build Coastguard Worker }
293*c2e18aaaSAndroid Build Coastguard Worker metrics.display_survey();
294*c2e18aaaSAndroid Build Coastguard Worker println!("New android update workflow tool available! go/a-update");
295*c2e18aaaSAndroid Build Coastguard Worker
296*c2e18aaaSAndroid Build Coastguard Worker Ok(())
297*c2e18aaaSAndroid Build Coastguard Worker }
298*c2e18aaaSAndroid Build Coastguard Worker
299*c2e18aaaSAndroid Build Coastguard Worker /// Returns the commands to update the device for every file that should be updated.
300*c2e18aaaSAndroid Build Coastguard Worker /// If there are errors, like some files in the staging set have not been built, then
301*c2e18aaaSAndroid Build Coastguard Worker /// an error result is returned.
302*c2e18aaaSAndroid Build Coastguard Worker #[allow(clippy::too_many_arguments)]
get_update_commands( device_tree: &HashMap<PathBuf, FileMetadata>, host_tree: &HashMap<PathBuf, FileMetadata>, ninja_installed_files: &[String], product_out: PathBuf, installed_packages: &HashSet<String>, diff_mode: DiffMode, partitions: &[PathBuf], force: bool, stdout: &mut impl Write, ) -> Result<commands::Commands>303*c2e18aaaSAndroid Build Coastguard Worker fn get_update_commands(
304*c2e18aaaSAndroid Build Coastguard Worker device_tree: &HashMap<PathBuf, FileMetadata>,
305*c2e18aaaSAndroid Build Coastguard Worker host_tree: &HashMap<PathBuf, FileMetadata>,
306*c2e18aaaSAndroid Build Coastguard Worker ninja_installed_files: &[String],
307*c2e18aaaSAndroid Build Coastguard Worker product_out: PathBuf,
308*c2e18aaaSAndroid Build Coastguard Worker installed_packages: &HashSet<String>,
309*c2e18aaaSAndroid Build Coastguard Worker diff_mode: DiffMode,
310*c2e18aaaSAndroid Build Coastguard Worker partitions: &[PathBuf],
311*c2e18aaaSAndroid Build Coastguard Worker force: bool,
312*c2e18aaaSAndroid Build Coastguard Worker stdout: &mut impl Write,
313*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<commands::Commands> {
314*c2e18aaaSAndroid Build Coastguard Worker // NOTE: The Ninja deps list can be _ahead_of_ the product tree output list.
315*c2e18aaaSAndroid Build Coastguard Worker // i.e. m `nothing` will update our ninja list even before someone
316*c2e18aaaSAndroid Build Coastguard Worker // does a build to populate product out.
317*c2e18aaaSAndroid Build Coastguard Worker // We don't have a way to know if we are in this case or if the user
318*c2e18aaaSAndroid Build Coastguard Worker // ever did a `m droid`
319*c2e18aaaSAndroid Build Coastguard Worker
320*c2e18aaaSAndroid Build Coastguard Worker // We add implicit dirs up to the partition name to the tracked set so the set matches the staging set.
321*c2e18aaaSAndroid Build Coastguard Worker let mut ninja_installed_dirs: HashSet<PathBuf> =
322*c2e18aaaSAndroid Build Coastguard Worker ninja_installed_files.iter().flat_map(|p| parents(p, partitions)).collect();
323*c2e18aaaSAndroid Build Coastguard Worker for p in partitions {
324*c2e18aaaSAndroid Build Coastguard Worker ninja_installed_dirs.insert(PathBuf::from(p));
325*c2e18aaaSAndroid Build Coastguard Worker }
326*c2e18aaaSAndroid Build Coastguard Worker
327*c2e18aaaSAndroid Build Coastguard Worker let tracked_set: HashSet<PathBuf> =
328*c2e18aaaSAndroid Build Coastguard Worker ninja_installed_files.iter().map(PathBuf::from).chain(ninja_installed_dirs).collect();
329*c2e18aaaSAndroid Build Coastguard Worker let host_set: HashSet<PathBuf> = host_tree.keys().map(PathBuf::clone).collect();
330*c2e18aaaSAndroid Build Coastguard Worker
331*c2e18aaaSAndroid Build Coastguard Worker // Files that are in the tracked set but NOT in the build directory. These need
332*c2e18aaaSAndroid Build Coastguard Worker // to be built.
333*c2e18aaaSAndroid Build Coastguard Worker let needs_building: HashSet<&PathBuf> = tracked_set.difference(&host_set).collect();
334*c2e18aaaSAndroid Build Coastguard Worker let status_per_file = &collect_status_per_file(
335*c2e18aaaSAndroid Build Coastguard Worker &tracked_set,
336*c2e18aaaSAndroid Build Coastguard Worker host_tree,
337*c2e18aaaSAndroid Build Coastguard Worker device_tree,
338*c2e18aaaSAndroid Build Coastguard Worker &product_out,
339*c2e18aaaSAndroid Build Coastguard Worker installed_packages,
340*c2e18aaaSAndroid Build Coastguard Worker diff_mode,
341*c2e18aaaSAndroid Build Coastguard Worker )?;
342*c2e18aaaSAndroid Build Coastguard Worker progress::stop();
343*c2e18aaaSAndroid Build Coastguard Worker print_status(stdout, status_per_file)?;
344*c2e18aaaSAndroid Build Coastguard Worker
345*c2e18aaaSAndroid Build Coastguard Worker // Shadow apks are apks that are installed outside the system partition with `adb install`
346*c2e18aaaSAndroid Build Coastguard Worker // If they exist, we should print instructions to uninstall and stop the update.
347*c2e18aaaSAndroid Build Coastguard Worker shadow_apk_check(stdout, status_per_file)?;
348*c2e18aaaSAndroid Build Coastguard Worker
349*c2e18aaaSAndroid Build Coastguard Worker #[allow(clippy::len_zero)]
350*c2e18aaaSAndroid Build Coastguard Worker if needs_building.len() > 0 {
351*c2e18aaaSAndroid Build Coastguard Worker if force {
352*c2e18aaaSAndroid Build Coastguard Worker println!("UNSAFE: The above modules should be built, but were not. This may cause the device to crash:\nProceeding due to \"--force\" flag.");
353*c2e18aaaSAndroid Build Coastguard Worker } else {
354*c2e18aaaSAndroid Build Coastguard Worker bail!("ERROR: Please build the above modules before updating.\nIf you want to continue anyway (which may cause the device to crash), rerun adevice with the \"--force\" flag.");
355*c2e18aaaSAndroid Build Coastguard Worker }
356*c2e18aaaSAndroid Build Coastguard Worker }
357*c2e18aaaSAndroid Build Coastguard Worker
358*c2e18aaaSAndroid Build Coastguard Worker // Restrict the host set down to the ones that are in the tracked set and not installed in the data partition.
359*c2e18aaaSAndroid Build Coastguard Worker let filtered_host_set: HashMap<PathBuf, FileMetadata> = host_tree
360*c2e18aaaSAndroid Build Coastguard Worker .iter()
361*c2e18aaaSAndroid Build Coastguard Worker .filter_map(|(key, value)| {
362*c2e18aaaSAndroid Build Coastguard Worker if tracked_set.contains(key) {
363*c2e18aaaSAndroid Build Coastguard Worker Some((key.clone(), value.clone()))
364*c2e18aaaSAndroid Build Coastguard Worker } else {
365*c2e18aaaSAndroid Build Coastguard Worker None
366*c2e18aaaSAndroid Build Coastguard Worker }
367*c2e18aaaSAndroid Build Coastguard Worker })
368*c2e18aaaSAndroid Build Coastguard Worker .collect();
369*c2e18aaaSAndroid Build Coastguard Worker
370*c2e18aaaSAndroid Build Coastguard Worker let filtered_changes = fingerprint::diff(&filtered_host_set, device_tree, diff_mode);
371*c2e18aaaSAndroid Build Coastguard Worker Ok(commands::compose(&filtered_changes, &product_out))
372*c2e18aaaSAndroid Build Coastguard Worker }
373*c2e18aaaSAndroid Build Coastguard Worker
374*c2e18aaaSAndroid Build Coastguard Worker // These are the partitions we will try to install to.
375*c2e18aaaSAndroid Build Coastguard Worker // ADB sync also has data, oem and vendor.
376*c2e18aaaSAndroid Build Coastguard Worker // There are some partition images (like boot.img) that we don't have a good way of determining
377*c2e18aaaSAndroid Build Coastguard Worker // the changed status of. (i.e. did they touch files that forces a flash/reimage).
378*c2e18aaaSAndroid Build Coastguard Worker // By default we will clean all the default partitions of stale files.
379*c2e18aaaSAndroid Build Coastguard Worker const DEFAULT_PARTITIONS: &[&str] = &["system", "system_ext", "odm", "product"];
380*c2e18aaaSAndroid Build Coastguard Worker
381*c2e18aaaSAndroid Build Coastguard Worker /// If a user explicitly passes a partition, but that doesn't exist in the tracked files,
382*c2e18aaaSAndroid Build Coastguard Worker /// then bail.
383*c2e18aaaSAndroid Build Coastguard Worker /// Otherwise, if one of the default partitions does not exist (like system_ext), then
384*c2e18aaaSAndroid Build Coastguard Worker /// just remove it from the default.
validate_partitions( partition_root: &Path, tracked_files: &[String], cli_partitions: &Option<Vec<String>>, ) -> Result<Vec<String>>385*c2e18aaaSAndroid Build Coastguard Worker fn validate_partitions(
386*c2e18aaaSAndroid Build Coastguard Worker partition_root: &Path,
387*c2e18aaaSAndroid Build Coastguard Worker tracked_files: &[String],
388*c2e18aaaSAndroid Build Coastguard Worker cli_partitions: &Option<Vec<String>>,
389*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<Vec<String>> {
390*c2e18aaaSAndroid Build Coastguard Worker // NOTE: We use PathBuf instead of String so starts_with matches path components.
391*c2e18aaaSAndroid Build Coastguard Worker // Use the partitions the user passed in or default to system and system_ext
392*c2e18aaaSAndroid Build Coastguard Worker if let Some(partitions) = cli_partitions {
393*c2e18aaaSAndroid Build Coastguard Worker for partition in partitions {
394*c2e18aaaSAndroid Build Coastguard Worker if !tracked_files.iter().any(|t| PathBuf::from(t).starts_with(partition)) {
395*c2e18aaaSAndroid Build Coastguard Worker bail!("{partition:?} is not a valid partition for current lunch target.");
396*c2e18aaaSAndroid Build Coastguard Worker }
397*c2e18aaaSAndroid Build Coastguard Worker }
398*c2e18aaaSAndroid Build Coastguard Worker for partition in partitions {
399*c2e18aaaSAndroid Build Coastguard Worker if fs::read_dir(partition_root.join(partition)).is_err() {
400*c2e18aaaSAndroid Build Coastguard Worker bail!("{partition:?} partition does not exist on host. Try rebuilding with m");
401*c2e18aaaSAndroid Build Coastguard Worker }
402*c2e18aaaSAndroid Build Coastguard Worker }
403*c2e18aaaSAndroid Build Coastguard Worker return Ok(partitions.clone());
404*c2e18aaaSAndroid Build Coastguard Worker }
405*c2e18aaaSAndroid Build Coastguard Worker let found_partitions: Vec<String> = DEFAULT_PARTITIONS
406*c2e18aaaSAndroid Build Coastguard Worker .iter()
407*c2e18aaaSAndroid Build Coastguard Worker .filter_map(|part| match tracked_files.iter().any(|t| PathBuf::from(t).starts_with(part)) {
408*c2e18aaaSAndroid Build Coastguard Worker true => Some(part.to_string()),
409*c2e18aaaSAndroid Build Coastguard Worker false => None,
410*c2e18aaaSAndroid Build Coastguard Worker })
411*c2e18aaaSAndroid Build Coastguard Worker .collect();
412*c2e18aaaSAndroid Build Coastguard Worker for partition in &found_partitions {
413*c2e18aaaSAndroid Build Coastguard Worker if fs::read_dir(partition_root.join(partition)).is_err() {
414*c2e18aaaSAndroid Build Coastguard Worker bail!("{partition:?} partition does not exist on host. Try rebuilding with m");
415*c2e18aaaSAndroid Build Coastguard Worker }
416*c2e18aaaSAndroid Build Coastguard Worker }
417*c2e18aaaSAndroid Build Coastguard Worker
418*c2e18aaaSAndroid Build Coastguard Worker Ok(found_partitions)
419*c2e18aaaSAndroid Build Coastguard Worker }
420*c2e18aaaSAndroid Build Coastguard Worker
421*c2e18aaaSAndroid Build Coastguard Worker #[derive(Clone, PartialEq)]
422*c2e18aaaSAndroid Build Coastguard Worker enum PushState {
423*c2e18aaaSAndroid Build Coastguard Worker Push,
424*c2e18aaaSAndroid Build Coastguard Worker /// File is tracked and the device and host fingerprints match.
425*c2e18aaaSAndroid Build Coastguard Worker UpToDate,
426*c2e18aaaSAndroid Build Coastguard Worker /// File is not tracked but exists on device and host.
427*c2e18aaaSAndroid Build Coastguard Worker TrackOrClean,
428*c2e18aaaSAndroid Build Coastguard Worker /// File is on the device, but not host and not tracked.
429*c2e18aaaSAndroid Build Coastguard Worker TrackAndBuildOrClean,
430*c2e18aaaSAndroid Build Coastguard Worker /// File is tracked and on host but not on device.
431*c2e18aaaSAndroid Build Coastguard Worker //PushNew,
432*c2e18aaaSAndroid Build Coastguard Worker /// File is on host, but not tracked and not on device.
433*c2e18aaaSAndroid Build Coastguard Worker TrackOrMakeClean,
434*c2e18aaaSAndroid Build Coastguard Worker /// File is tracked and on the device, but is not in the build tree.
435*c2e18aaaSAndroid Build Coastguard Worker /// `m` the module to build it.
436*c2e18aaaSAndroid Build Coastguard Worker UntrackOrBuild,
437*c2e18aaaSAndroid Build Coastguard Worker /// The apk was `installed` on top of the system image. It will shadow any push
438*c2e18aaaSAndroid Build Coastguard Worker /// we make to the system partitions. It should be explicitly installed or uninstalled, not pushed.
439*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Store package name and path to file on disk so we can print a better
440*c2e18aaaSAndroid Build Coastguard Worker // message to the user.
441*c2e18aaaSAndroid Build Coastguard Worker ApkInstalled,
442*c2e18aaaSAndroid Build Coastguard Worker }
443*c2e18aaaSAndroid Build Coastguard Worker
444*c2e18aaaSAndroid Build Coastguard Worker impl PushState {
445*c2e18aaaSAndroid Build Coastguard Worker /// Message to print indicating what actions the user should take based on the
446*c2e18aaaSAndroid Build Coastguard Worker /// state of the file.
get_action_msg(self) -> String447*c2e18aaaSAndroid Build Coastguard Worker pub fn get_action_msg(self) -> String {
448*c2e18aaaSAndroid Build Coastguard Worker match self {
449*c2e18aaaSAndroid Build Coastguard Worker PushState::Push => "Ready to push:\n (These files are out of date on the device and will be pushed when you run `adevice update`)".to_string(),
450*c2e18aaaSAndroid Build Coastguard Worker // Note: we don't print up to date files.
451*c2e18aaaSAndroid Build Coastguard Worker PushState::UpToDate => "Up to date: (These files are up to date on the device. There is nothing to do.)".to_string(),
452*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackOrClean => "Untracked pushed files:\n (These files are not tracked but exist on the device and host.)\n (Use `adevice track` for the appropriate module to have them pushed.)".to_string(),
453*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackAndBuildOrClean => "Stale device files:\n (These files are on the device, but not built or tracked.)\n (They will be cleaned with `adevice update` or `adevice clean`.)".to_string(),
454*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackOrMakeClean => "Untracked built files:\n (These files are in the build tree but not tracked or on the device.)\n (You might want to `adevice track` the module. It is safe to do nothing.)".to_string(),
455*c2e18aaaSAndroid Build Coastguard Worker PushState::UntrackOrBuild => "Unbuilt files:\n (These files should be built so the device can be updated.)\n (Rebuild and `adevice update`)".to_string(),
456*c2e18aaaSAndroid Build Coastguard Worker PushState::ApkInstalled => format!("ADB Installed files:\n{RED_WARNING_LINE} (These files were installed with `adb install` or similar. Pushing to the system partition will not make them available.)\n (Either `adb uninstall` these packages or `adb install` by hand.`)"),
457*c2e18aaaSAndroid Build Coastguard Worker }
458*c2e18aaaSAndroid Build Coastguard Worker }
459*c2e18aaaSAndroid Build Coastguard Worker }
460*c2e18aaaSAndroid Build Coastguard Worker
461*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Create a struct for each of the sections above for better formatting.
462*c2e18aaaSAndroid Build Coastguard Worker const RED_WARNING_LINE: &str = " \x1b[1;31m!! Warning: !!\x1b[0m\n";
463*c2e18aaaSAndroid Build Coastguard Worker
464*c2e18aaaSAndroid Build Coastguard Worker /// Group each file by state and print the state message followed by the files in that state.
print_status(stdout: &mut impl Write, files: &HashMap<PathBuf, PushState>) -> Result<()>465*c2e18aaaSAndroid Build Coastguard Worker fn print_status(stdout: &mut impl Write, files: &HashMap<PathBuf, PushState>) -> Result<()> {
466*c2e18aaaSAndroid Build Coastguard Worker for state in [
467*c2e18aaaSAndroid Build Coastguard Worker PushState::Push,
468*c2e18aaaSAndroid Build Coastguard Worker // Skip UpToDate and TrackOrMakeClean, don't print those.
469*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackOrClean,
470*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackAndBuildOrClean,
471*c2e18aaaSAndroid Build Coastguard Worker PushState::UntrackOrBuild,
472*c2e18aaaSAndroid Build Coastguard Worker // Skip APKInstalled, it is handleded in shadow_apk_check.
473*c2e18aaaSAndroid Build Coastguard Worker ] {
474*c2e18aaaSAndroid Build Coastguard Worker print_files_in_state(stdout, files, state)?;
475*c2e18aaaSAndroid Build Coastguard Worker }
476*c2e18aaaSAndroid Build Coastguard Worker Ok(())
477*c2e18aaaSAndroid Build Coastguard Worker }
478*c2e18aaaSAndroid Build Coastguard Worker
479*c2e18aaaSAndroid Build Coastguard Worker /// Determine if file is an apk and decide if we need to give a warning
480*c2e18aaaSAndroid Build Coastguard Worker /// about pushing to a system directory because it is already installed in /data
481*c2e18aaaSAndroid Build Coastguard Worker /// and will shadow a system apk if we push it.
installed_apk_action( file: &Path, product_out: &Path, installed_packages: &HashSet<String>, ) -> Result<PushState>482*c2e18aaaSAndroid Build Coastguard Worker fn installed_apk_action(
483*c2e18aaaSAndroid Build Coastguard Worker file: &Path,
484*c2e18aaaSAndroid Build Coastguard Worker product_out: &Path,
485*c2e18aaaSAndroid Build Coastguard Worker installed_packages: &HashSet<String>,
486*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<PushState> {
487*c2e18aaaSAndroid Build Coastguard Worker if file.extension() != Some(OsString::from("apk").as_os_str()) {
488*c2e18aaaSAndroid Build Coastguard Worker return Ok(PushState::Push);
489*c2e18aaaSAndroid Build Coastguard Worker }
490*c2e18aaaSAndroid Build Coastguard Worker // See if this file was installed.
491*c2e18aaaSAndroid Build Coastguard Worker if is_apk_installed(&product_out.join(file), installed_packages)? {
492*c2e18aaaSAndroid Build Coastguard Worker Ok(PushState::ApkInstalled)
493*c2e18aaaSAndroid Build Coastguard Worker } else {
494*c2e18aaaSAndroid Build Coastguard Worker Ok(PushState::Push)
495*c2e18aaaSAndroid Build Coastguard Worker }
496*c2e18aaaSAndroid Build Coastguard Worker }
497*c2e18aaaSAndroid Build Coastguard Worker
498*c2e18aaaSAndroid Build Coastguard Worker /// Determine if the given apk has been installed via `adb install`.
499*c2e18aaaSAndroid Build Coastguard Worker /// This will allow us to decide if pushing to /system will cause problems because the
500*c2e18aaaSAndroid Build Coastguard Worker /// version we push would be shadowed by the `installed` version.
501*c2e18aaaSAndroid Build Coastguard Worker /// Run PackageManager commands from the shell to check if something is installed.
502*c2e18aaaSAndroid Build Coastguard Worker /// If this is a problem, we can build something in to adevice_fingerprint that
503*c2e18aaaSAndroid Build Coastguard Worker /// calls PackageManager#getInstalledApplications.
504*c2e18aaaSAndroid Build Coastguard Worker /// adb exec-out pm list packages -s -f
is_apk_installed(host_path: &Path, installed_packages: &HashSet<String>) -> Result<bool>505*c2e18aaaSAndroid Build Coastguard Worker fn is_apk_installed(host_path: &Path, installed_packages: &HashSet<String>) -> Result<bool> {
506*c2e18aaaSAndroid Build Coastguard Worker let host_apk_path = host_path.as_os_str().to_str().unwrap();
507*c2e18aaaSAndroid Build Coastguard Worker let aapt_output = std::process::Command::new("aapt2")
508*c2e18aaaSAndroid Build Coastguard Worker .args(["dump", "permissions", host_apk_path])
509*c2e18aaaSAndroid Build Coastguard Worker .output()
510*c2e18aaaSAndroid Build Coastguard Worker .context(format!("Running aapt2 on host to see if apk installed: {}", host_apk_path))?;
511*c2e18aaaSAndroid Build Coastguard Worker
512*c2e18aaaSAndroid Build Coastguard Worker if !aapt_output.status.success() {
513*c2e18aaaSAndroid Build Coastguard Worker let stderr = String::from_utf8(aapt_output.stderr)?;
514*c2e18aaaSAndroid Build Coastguard Worker bail!("Unable to run aapt2 to get installed packages {:?}", stderr);
515*c2e18aaaSAndroid Build Coastguard Worker }
516*c2e18aaaSAndroid Build Coastguard Worker
517*c2e18aaaSAndroid Build Coastguard Worker match package_from_aapt_dump_output(aapt_output.stdout) {
518*c2e18aaaSAndroid Build Coastguard Worker Ok(package) => {
519*c2e18aaaSAndroid Build Coastguard Worker debug!("AAPT dump found package: {package}");
520*c2e18aaaSAndroid Build Coastguard Worker Ok(installed_packages.contains(&package))
521*c2e18aaaSAndroid Build Coastguard Worker }
522*c2e18aaaSAndroid Build Coastguard Worker Err(e) => bail!("Unable to run aapt2 to get package information {e:?}"),
523*c2e18aaaSAndroid Build Coastguard Worker }
524*c2e18aaaSAndroid Build Coastguard Worker }
525*c2e18aaaSAndroid Build Coastguard Worker
526*c2e18aaaSAndroid Build Coastguard Worker static AAPT_PACKAGE_MATCHER: LazyLock<Regex> =
527*c2e18aaaSAndroid Build Coastguard Worker LazyLock::new(|| Regex::new(r"^package: (.+)$").expect("regex does not compile"));
528*c2e18aaaSAndroid Build Coastguard Worker
529*c2e18aaaSAndroid Build Coastguard Worker /// Filter aapt2 dump output to parse out the package name for the apk.
package_from_aapt_dump_output(stdout: Vec<u8>) -> Result<String>530*c2e18aaaSAndroid Build Coastguard Worker fn package_from_aapt_dump_output(stdout: Vec<u8>) -> Result<String> {
531*c2e18aaaSAndroid Build Coastguard Worker let package_match = String::from_utf8(stdout)?
532*c2e18aaaSAndroid Build Coastguard Worker .lines()
533*c2e18aaaSAndroid Build Coastguard Worker .filter_map(|line| AAPT_PACKAGE_MATCHER.captures(line).map(|x| x[1].to_string()))
534*c2e18aaaSAndroid Build Coastguard Worker .collect();
535*c2e18aaaSAndroid Build Coastguard Worker Ok(package_match)
536*c2e18aaaSAndroid Build Coastguard Worker }
537*c2e18aaaSAndroid Build Coastguard Worker
538*c2e18aaaSAndroid Build Coastguard Worker /// Go through all files that exist on the host, device, and tracking set.
539*c2e18aaaSAndroid Build Coastguard Worker /// Ignore any file that is in all three and has the same fingerprint on the host and device.
540*c2e18aaaSAndroid Build Coastguard Worker /// States where the user should take action:
541*c2e18aaaSAndroid Build Coastguard Worker /// Build
542*c2e18aaaSAndroid Build Coastguard Worker /// Clean
543*c2e18aaaSAndroid Build Coastguard Worker /// Track
544*c2e18aaaSAndroid Build Coastguard Worker /// Untrack
collect_status_per_file( tracked_set: &HashSet<PathBuf>, host_tree: &HashMap<PathBuf, FileMetadata>, device_tree: &HashMap<PathBuf, FileMetadata>, product_out: &Path, installed_packages: &HashSet<String>, diff_mode: DiffMode, ) -> Result<HashMap<PathBuf, PushState>>545*c2e18aaaSAndroid Build Coastguard Worker fn collect_status_per_file(
546*c2e18aaaSAndroid Build Coastguard Worker tracked_set: &HashSet<PathBuf>,
547*c2e18aaaSAndroid Build Coastguard Worker host_tree: &HashMap<PathBuf, FileMetadata>,
548*c2e18aaaSAndroid Build Coastguard Worker device_tree: &HashMap<PathBuf, FileMetadata>,
549*c2e18aaaSAndroid Build Coastguard Worker product_out: &Path,
550*c2e18aaaSAndroid Build Coastguard Worker installed_packages: &HashSet<String>,
551*c2e18aaaSAndroid Build Coastguard Worker diff_mode: DiffMode,
552*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<HashMap<PathBuf, PushState>> {
553*c2e18aaaSAndroid Build Coastguard Worker let mut all_files: Vec<&PathBuf> =
554*c2e18aaaSAndroid Build Coastguard Worker host_tree.keys().chain(device_tree.keys()).chain(tracked_set.iter()).collect();
555*c2e18aaaSAndroid Build Coastguard Worker all_files.dedup();
556*c2e18aaaSAndroid Build Coastguard Worker
557*c2e18aaaSAndroid Build Coastguard Worker let states: HashMap<PathBuf, PushState> = all_files
558*c2e18aaaSAndroid Build Coastguard Worker .par_iter()
559*c2e18aaaSAndroid Build Coastguard Worker .map(|f| {
560*c2e18aaaSAndroid Build Coastguard Worker let on_device = device_tree.contains_key(*f);
561*c2e18aaaSAndroid Build Coastguard Worker let on_host = host_tree.contains_key(*f);
562*c2e18aaaSAndroid Build Coastguard Worker let tracked = tracked_set.contains(*f);
563*c2e18aaaSAndroid Build Coastguard Worker
564*c2e18aaaSAndroid Build Coastguard Worker // I think keeping tracked/untracked else is clearer than collapsing.
565*c2e18aaaSAndroid Build Coastguard Worker #[allow(clippy::collapsible_else_if)]
566*c2e18aaaSAndroid Build Coastguard Worker let push_state = if tracked {
567*c2e18aaaSAndroid Build Coastguard Worker if on_device && on_host {
568*c2e18aaaSAndroid Build Coastguard Worker if fingerprint::is_metadata_diff(
569*c2e18aaaSAndroid Build Coastguard Worker device_tree.get(*f).unwrap(),
570*c2e18aaaSAndroid Build Coastguard Worker host_tree.get(*f).unwrap(),
571*c2e18aaaSAndroid Build Coastguard Worker diff_mode,
572*c2e18aaaSAndroid Build Coastguard Worker ) {
573*c2e18aaaSAndroid Build Coastguard Worker // PushDiff
574*c2e18aaaSAndroid Build Coastguard Worker installed_apk_action(f, product_out, installed_packages).expect("checking if apk installed")
575*c2e18aaaSAndroid Build Coastguard Worker } else {
576*c2e18aaaSAndroid Build Coastguard Worker // Else normal case, do nothing.
577*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Do we need to check for installed apk and warn.
578*c2e18aaaSAndroid Build Coastguard Worker // 1) User updates apk
579*c2e18aaaSAndroid Build Coastguard Worker // 2) User adb install
580*c2e18aaaSAndroid Build Coastguard Worker // 3) User reverts code and builds
581*c2e18aaaSAndroid Build Coastguard Worker // (host and device match but installed apk shadows system version).
582*c2e18aaaSAndroid Build Coastguard Worker // For now, don't look for extra problems.
583*c2e18aaaSAndroid Build Coastguard Worker PushState::UpToDate
584*c2e18aaaSAndroid Build Coastguard Worker }
585*c2e18aaaSAndroid Build Coastguard Worker } else if !on_host {
586*c2e18aaaSAndroid Build Coastguard Worker // We don't care if it is on the device or not, it has to built if it isn't
587*c2e18aaaSAndroid Build Coastguard Worker // on the host.
588*c2e18aaaSAndroid Build Coastguard Worker PushState::UntrackOrBuild
589*c2e18aaaSAndroid Build Coastguard Worker } else {
590*c2e18aaaSAndroid Build Coastguard Worker assert!(
591*c2e18aaaSAndroid Build Coastguard Worker !on_device && on_host,
592*c2e18aaaSAndroid Build Coastguard Worker "Unexpected state for file: {f:?}, tracked: {tracked} on_device: {on_device}, on_host: {on_host}"
593*c2e18aaaSAndroid Build Coastguard Worker );
594*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Is it possible for an apk to be adb installed, but not in the system image?
595*c2e18aaaSAndroid Build Coastguard Worker // I guess so, but seems weird. Add check InstalledApk here too.
596*c2e18aaaSAndroid Build Coastguard Worker // PushNew
597*c2e18aaaSAndroid Build Coastguard Worker PushState::Push
598*c2e18aaaSAndroid Build Coastguard Worker }
599*c2e18aaaSAndroid Build Coastguard Worker } else {
600*c2e18aaaSAndroid Build Coastguard Worker if on_device && on_host {
601*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackOrClean
602*c2e18aaaSAndroid Build Coastguard Worker } else if on_device && !on_host {
603*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackAndBuildOrClean
604*c2e18aaaSAndroid Build Coastguard Worker } else {
605*c2e18aaaSAndroid Build Coastguard Worker // Note: case of !tracked, !on_host, !on_device is not possible.
606*c2e18aaaSAndroid Build Coastguard Worker // So only one case left.
607*c2e18aaaSAndroid Build Coastguard Worker assert!(
608*c2e18aaaSAndroid Build Coastguard Worker !on_device && on_host,
609*c2e18aaaSAndroid Build Coastguard Worker "Unexpected state for file: {f:?}, tracked: {tracked} on_device: {on_device}, on_host: {on_host}"
610*c2e18aaaSAndroid Build Coastguard Worker );
611*c2e18aaaSAndroid Build Coastguard Worker PushState::TrackOrMakeClean
612*c2e18aaaSAndroid Build Coastguard Worker }
613*c2e18aaaSAndroid Build Coastguard Worker };
614*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from(f), push_state)
615*c2e18aaaSAndroid Build Coastguard Worker })
616*c2e18aaaSAndroid Build Coastguard Worker .collect();
617*c2e18aaaSAndroid Build Coastguard Worker Ok(states)
618*c2e18aaaSAndroid Build Coastguard Worker }
619*c2e18aaaSAndroid Build Coastguard Worker
620*c2e18aaaSAndroid Build Coastguard Worker /// Find all files in a given state, and if that file list is not empty, print the
621*c2e18aaaSAndroid Build Coastguard Worker /// state message and all the files (sorted).
622*c2e18aaaSAndroid Build Coastguard Worker /// Only prints stages that files in that stage.
print_files_in_state( stdout: &mut impl Write, files: &HashMap<PathBuf, PushState>, push_state: PushState, ) -> Result<()>623*c2e18aaaSAndroid Build Coastguard Worker fn print_files_in_state(
624*c2e18aaaSAndroid Build Coastguard Worker stdout: &mut impl Write,
625*c2e18aaaSAndroid Build Coastguard Worker files: &HashMap<PathBuf, PushState>,
626*c2e18aaaSAndroid Build Coastguard Worker push_state: PushState,
627*c2e18aaaSAndroid Build Coastguard Worker ) -> Result<()> {
628*c2e18aaaSAndroid Build Coastguard Worker let filtered_files: HashMap<&PathBuf, &PushState> =
629*c2e18aaaSAndroid Build Coastguard Worker files.iter().filter(|(_, state)| *state == &push_state).collect();
630*c2e18aaaSAndroid Build Coastguard Worker
631*c2e18aaaSAndroid Build Coastguard Worker if filtered_files.is_empty() {
632*c2e18aaaSAndroid Build Coastguard Worker return Ok(());
633*c2e18aaaSAndroid Build Coastguard Worker }
634*c2e18aaaSAndroid Build Coastguard Worker writeln!(stdout, "{}", &push_state.get_action_msg())?;
635*c2e18aaaSAndroid Build Coastguard Worker let file_list_output = filtered_files
636*c2e18aaaSAndroid Build Coastguard Worker .keys()
637*c2e18aaaSAndroid Build Coastguard Worker .sorted()
638*c2e18aaaSAndroid Build Coastguard Worker .map(|path| format!("\t{}", path.display()))
639*c2e18aaaSAndroid Build Coastguard Worker .collect::<Vec<String>>()
640*c2e18aaaSAndroid Build Coastguard Worker .join("\n");
641*c2e18aaaSAndroid Build Coastguard Worker writeln!(stdout, "{}", file_list_output)?;
642*c2e18aaaSAndroid Build Coastguard Worker Ok(())
643*c2e18aaaSAndroid Build Coastguard Worker }
644*c2e18aaaSAndroid Build Coastguard Worker
get_product_out_from_env() -> Option<PathBuf>645*c2e18aaaSAndroid Build Coastguard Worker fn get_product_out_from_env() -> Option<PathBuf> {
646*c2e18aaaSAndroid Build Coastguard Worker match std::env::var("ANDROID_PRODUCT_OUT") {
647*c2e18aaaSAndroid Build Coastguard Worker Ok(x) if !x.is_empty() => Some(PathBuf::from(x)),
648*c2e18aaaSAndroid Build Coastguard Worker _ => None,
649*c2e18aaaSAndroid Build Coastguard Worker }
650*c2e18aaaSAndroid Build Coastguard Worker }
651*c2e18aaaSAndroid Build Coastguard Worker
652*c2e18aaaSAndroid Build Coastguard Worker /// Prints uninstall commands for every package installed
653*c2e18aaaSAndroid Build Coastguard Worker /// Bails if there are any installed packages.
shadow_apk_check(stdout: &mut impl Write, files: &HashMap<PathBuf, PushState>) -> Result<()>654*c2e18aaaSAndroid Build Coastguard Worker fn shadow_apk_check(stdout: &mut impl Write, files: &HashMap<PathBuf, PushState>) -> Result<()> {
655*c2e18aaaSAndroid Build Coastguard Worker let filtered_files: HashMap<&PathBuf, &PushState> =
656*c2e18aaaSAndroid Build Coastguard Worker files.iter().filter(|(_, state)| *state == &PushState::ApkInstalled).collect();
657*c2e18aaaSAndroid Build Coastguard Worker
658*c2e18aaaSAndroid Build Coastguard Worker if filtered_files.is_empty() {
659*c2e18aaaSAndroid Build Coastguard Worker return Ok(());
660*c2e18aaaSAndroid Build Coastguard Worker }
661*c2e18aaaSAndroid Build Coastguard Worker
662*c2e18aaaSAndroid Build Coastguard Worker writeln!(stdout, "{}", PushState::ApkInstalled.get_action_msg())?;
663*c2e18aaaSAndroid Build Coastguard Worker let file_list_output = filtered_files
664*c2e18aaaSAndroid Build Coastguard Worker .keys()
665*c2e18aaaSAndroid Build Coastguard Worker .sorted()
666*c2e18aaaSAndroid Build Coastguard Worker .map(|path| format!("adb uninstall {};", path.display()))
667*c2e18aaaSAndroid Build Coastguard Worker .collect::<Vec<String>>()
668*c2e18aaaSAndroid Build Coastguard Worker .join("\n");
669*c2e18aaaSAndroid Build Coastguard Worker writeln!(stdout, "{}", file_list_output)?;
670*c2e18aaaSAndroid Build Coastguard Worker bail!("{} shadowing apks found. Uninstall to continue.", filtered_files.keys().len());
671*c2e18aaaSAndroid Build Coastguard Worker }
672*c2e18aaaSAndroid Build Coastguard Worker
673*c2e18aaaSAndroid Build Coastguard Worker /// Return all path components of file_path up to a passed partition.
674*c2e18aaaSAndroid Build Coastguard Worker /// Given system/bin/logd and partition "system",
675*c2e18aaaSAndroid Build Coastguard Worker /// return ["system/bin/logd", "system/bin"], not "system" or ""
676*c2e18aaaSAndroid Build Coastguard Worker
parents(file_path: &str, partitions: &[PathBuf]) -> Vec<PathBuf>677*c2e18aaaSAndroid Build Coastguard Worker fn parents(file_path: &str, partitions: &[PathBuf]) -> Vec<PathBuf> {
678*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from(file_path)
679*c2e18aaaSAndroid Build Coastguard Worker .ancestors()
680*c2e18aaaSAndroid Build Coastguard Worker .map(|p| p.to_path_buf())
681*c2e18aaaSAndroid Build Coastguard Worker .take_while(|p| !partitions.contains(p))
682*c2e18aaaSAndroid Build Coastguard Worker .collect()
683*c2e18aaaSAndroid Build Coastguard Worker }
684*c2e18aaaSAndroid Build Coastguard Worker
685*c2e18aaaSAndroid Build Coastguard Worker #[allow(missing_docs)]
686*c2e18aaaSAndroid Build Coastguard Worker #[derive(Default)]
687*c2e18aaaSAndroid Build Coastguard Worker pub struct Profiler {
688*c2e18aaaSAndroid Build Coastguard Worker pub device_fingerprint: Duration,
689*c2e18aaaSAndroid Build Coastguard Worker pub host_fingerprint: Duration,
690*c2e18aaaSAndroid Build Coastguard Worker pub ninja_deps_computer: Duration,
691*c2e18aaaSAndroid Build Coastguard Worker /// Time to run all the "adb push" or "adb rm" commands.
692*c2e18aaaSAndroid Build Coastguard Worker pub adb_cmds: Duration,
693*c2e18aaaSAndroid Build Coastguard Worker /// Time to run "adb reboot" or "exec-out start".
694*c2e18aaaSAndroid Build Coastguard Worker pub restart: Duration,
695*c2e18aaaSAndroid Build Coastguard Worker pub restart_type: String,
696*c2e18aaaSAndroid Build Coastguard Worker /// Time for device to respond to "wait-for-device".
697*c2e18aaaSAndroid Build Coastguard Worker pub wait_for_device: Duration,
698*c2e18aaaSAndroid Build Coastguard Worker /// Time for sys.boot_completed to be 1 after wait-for-device.
699*c2e18aaaSAndroid Build Coastguard Worker pub wait_for_boot_completed: Duration,
700*c2e18aaaSAndroid Build Coastguard Worker /// The first time after a userdebug build is flashed/created, we need
701*c2e18aaaSAndroid Build Coastguard Worker /// to mount rw and reboot.
702*c2e18aaaSAndroid Build Coastguard Worker pub first_remount_rw: Duration,
703*c2e18aaaSAndroid Build Coastguard Worker pub total: Duration,
704*c2e18aaaSAndroid Build Coastguard Worker }
705*c2e18aaaSAndroid Build Coastguard Worker
706*c2e18aaaSAndroid Build Coastguard Worker impl std::fmt::Display for Profiler {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result707*c2e18aaaSAndroid Build Coastguard Worker fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708*c2e18aaaSAndroid Build Coastguard Worker write!(
709*c2e18aaaSAndroid Build Coastguard Worker f,
710*c2e18aaaSAndroid Build Coastguard Worker "{}",
711*c2e18aaaSAndroid Build Coastguard Worker [
712*c2e18aaaSAndroid Build Coastguard Worker " Operation profile: (secs)".to_string(),
713*c2e18aaaSAndroid Build Coastguard Worker format!("Device Fingerprint - {}", self.device_fingerprint.as_secs()),
714*c2e18aaaSAndroid Build Coastguard Worker format!("Host fingerprint - {}", self.host_fingerprint.as_secs()),
715*c2e18aaaSAndroid Build Coastguard Worker format!("Ninja - {}", self.ninja_deps_computer.as_secs()),
716*c2e18aaaSAndroid Build Coastguard Worker format!("Adb Cmds - {}", self.adb_cmds.as_secs()),
717*c2e18aaaSAndroid Build Coastguard Worker format!("Restart({})- {}", self.restart_type, self.restart.as_secs()),
718*c2e18aaaSAndroid Build Coastguard Worker format!("Wait For device connected - {}", self.wait_for_device.as_secs()),
719*c2e18aaaSAndroid Build Coastguard Worker format!("Wait For boot completed - {}", self.wait_for_boot_completed.as_secs()),
720*c2e18aaaSAndroid Build Coastguard Worker format!("First remount RW - {}", self.first_remount_rw.as_secs()),
721*c2e18aaaSAndroid Build Coastguard Worker format!("TOTAL - {}", self.total.as_secs()),
722*c2e18aaaSAndroid Build Coastguard Worker ]
723*c2e18aaaSAndroid Build Coastguard Worker .join("\n\t")
724*c2e18aaaSAndroid Build Coastguard Worker )
725*c2e18aaaSAndroid Build Coastguard Worker }
726*c2e18aaaSAndroid Build Coastguard Worker }
727*c2e18aaaSAndroid Build Coastguard Worker
728*c2e18aaaSAndroid Build Coastguard Worker #[cfg(test)]
729*c2e18aaaSAndroid Build Coastguard Worker mod tests {
730*c2e18aaaSAndroid Build Coastguard Worker use super::*;
731*c2e18aaaSAndroid Build Coastguard Worker use crate::fingerprint::{self, DiffMode};
732*c2e18aaaSAndroid Build Coastguard Worker use std::path::PathBuf;
733*c2e18aaaSAndroid Build Coastguard Worker use tempfile::TempDir;
734*c2e18aaaSAndroid Build Coastguard Worker
735*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Capture/test stdout and logging.
736*c2e18aaaSAndroid Build Coastguard Worker // Test stdout: https://users.rust-lang.org/t/how-to-test-functions-that-use-println/67188/5
737*c2e18aaaSAndroid Build Coastguard Worker #[test]
empty_inputs() -> Result<()>738*c2e18aaaSAndroid Build Coastguard Worker fn empty_inputs() -> Result<()> {
739*c2e18aaaSAndroid Build Coastguard Worker let device_files: HashMap<PathBuf, FileMetadata> = HashMap::new();
740*c2e18aaaSAndroid Build Coastguard Worker let host_files: HashMap<PathBuf, FileMetadata> = HashMap::new();
741*c2e18aaaSAndroid Build Coastguard Worker let ninja_deps: Vec<String> = vec![];
742*c2e18aaaSAndroid Build Coastguard Worker let product_out = PathBuf::from("");
743*c2e18aaaSAndroid Build Coastguard Worker let installed_apks = HashSet::<String>::new();
744*c2e18aaaSAndroid Build Coastguard Worker let partitions = Vec::new();
745*c2e18aaaSAndroid Build Coastguard Worker let force = false;
746*c2e18aaaSAndroid Build Coastguard Worker let mut stdout = Vec::new();
747*c2e18aaaSAndroid Build Coastguard Worker
748*c2e18aaaSAndroid Build Coastguard Worker let results = get_update_commands(
749*c2e18aaaSAndroid Build Coastguard Worker &device_files,
750*c2e18aaaSAndroid Build Coastguard Worker &host_files,
751*c2e18aaaSAndroid Build Coastguard Worker &ninja_deps,
752*c2e18aaaSAndroid Build Coastguard Worker product_out,
753*c2e18aaaSAndroid Build Coastguard Worker &installed_apks,
754*c2e18aaaSAndroid Build Coastguard Worker DiffMode::UsePermissions,
755*c2e18aaaSAndroid Build Coastguard Worker &partitions,
756*c2e18aaaSAndroid Build Coastguard Worker force,
757*c2e18aaaSAndroid Build Coastguard Worker &mut stdout,
758*c2e18aaaSAndroid Build Coastguard Worker )?;
759*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(results.upserts.values().len(), 0);
760*c2e18aaaSAndroid Build Coastguard Worker Ok(())
761*c2e18aaaSAndroid Build Coastguard Worker }
762*c2e18aaaSAndroid Build Coastguard Worker
763*c2e18aaaSAndroid Build Coastguard Worker #[test]
host_and_ninja_file_not_on_device() -> Result<()>764*c2e18aaaSAndroid Build Coastguard Worker fn host_and_ninja_file_not_on_device() -> Result<()> {
765*c2e18aaaSAndroid Build Coastguard Worker // Relative to product out?
766*c2e18aaaSAndroid Build Coastguard Worker let product_out = PathBuf::from("");
767*c2e18aaaSAndroid Build Coastguard Worker let installed_apks = HashSet::<String>::new();
768*c2e18aaaSAndroid Build Coastguard Worker let partitions = Vec::new();
769*c2e18aaaSAndroid Build Coastguard Worker let mut stdout = Vec::new();
770*c2e18aaaSAndroid Build Coastguard Worker let force = true;
771*c2e18aaaSAndroid Build Coastguard Worker
772*c2e18aaaSAndroid Build Coastguard Worker let results = get_update_commands(
773*c2e18aaaSAndroid Build Coastguard Worker // Device files
774*c2e18aaaSAndroid Build Coastguard Worker &HashMap::new(),
775*c2e18aaaSAndroid Build Coastguard Worker // Host files
776*c2e18aaaSAndroid Build Coastguard Worker &HashMap::from([
777*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("system/myfile"), file_metadata("digest1")),
778*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("system"), dir_metadata()),
779*c2e18aaaSAndroid Build Coastguard Worker ]),
780*c2e18aaaSAndroid Build Coastguard Worker // Ninja deps
781*c2e18aaaSAndroid Build Coastguard Worker &["system".to_string(), "system/myfile".to_string()],
782*c2e18aaaSAndroid Build Coastguard Worker product_out,
783*c2e18aaaSAndroid Build Coastguard Worker &installed_apks,
784*c2e18aaaSAndroid Build Coastguard Worker DiffMode::UsePermissions,
785*c2e18aaaSAndroid Build Coastguard Worker &partitions,
786*c2e18aaaSAndroid Build Coastguard Worker force,
787*c2e18aaaSAndroid Build Coastguard Worker &mut stdout,
788*c2e18aaaSAndroid Build Coastguard Worker )?;
789*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(results.upserts.values().len(), 2);
790*c2e18aaaSAndroid Build Coastguard Worker Ok(())
791*c2e18aaaSAndroid Build Coastguard Worker }
792*c2e18aaaSAndroid Build Coastguard Worker
793*c2e18aaaSAndroid Build Coastguard Worker #[test]
host_and_ninja_file_not_on_device_force_false() -> Result<()>794*c2e18aaaSAndroid Build Coastguard Worker fn host_and_ninja_file_not_on_device_force_false() -> Result<()> {
795*c2e18aaaSAndroid Build Coastguard Worker let product_out = PathBuf::from("");
796*c2e18aaaSAndroid Build Coastguard Worker let installed_apks = HashSet::<String>::new();
797*c2e18aaaSAndroid Build Coastguard Worker let partitions = Vec::new();
798*c2e18aaaSAndroid Build Coastguard Worker let mut stdout = Vec::new();
799*c2e18aaaSAndroid Build Coastguard Worker let force = false;
800*c2e18aaaSAndroid Build Coastguard Worker
801*c2e18aaaSAndroid Build Coastguard Worker let results = get_update_commands(
802*c2e18aaaSAndroid Build Coastguard Worker // Device files
803*c2e18aaaSAndroid Build Coastguard Worker &HashMap::new(),
804*c2e18aaaSAndroid Build Coastguard Worker // Host files
805*c2e18aaaSAndroid Build Coastguard Worker &HashMap::from([
806*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("system/myfile"), file_metadata("digest1")),
807*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("system"), dir_metadata()),
808*c2e18aaaSAndroid Build Coastguard Worker ]),
809*c2e18aaaSAndroid Build Coastguard Worker // Ninja deps
810*c2e18aaaSAndroid Build Coastguard Worker &["system".to_string(), "system/myfile".to_string()],
811*c2e18aaaSAndroid Build Coastguard Worker product_out,
812*c2e18aaaSAndroid Build Coastguard Worker &installed_apks,
813*c2e18aaaSAndroid Build Coastguard Worker DiffMode::UsePermissions,
814*c2e18aaaSAndroid Build Coastguard Worker &partitions,
815*c2e18aaaSAndroid Build Coastguard Worker force,
816*c2e18aaaSAndroid Build Coastguard Worker &mut stdout,
817*c2e18aaaSAndroid Build Coastguard Worker );
818*c2e18aaaSAndroid Build Coastguard Worker assert!(results.is_err());
819*c2e18aaaSAndroid Build Coastguard Worker if let Err(e) = results {
820*c2e18aaaSAndroid Build Coastguard Worker assert!(e
821*c2e18aaaSAndroid Build Coastguard Worker .to_string()
822*c2e18aaaSAndroid Build Coastguard Worker .contains("ERROR: Please build the above modules before updating."));
823*c2e18aaaSAndroid Build Coastguard Worker }
824*c2e18aaaSAndroid Build Coastguard Worker Ok(())
825*c2e18aaaSAndroid Build Coastguard Worker }
826*c2e18aaaSAndroid Build Coastguard Worker
827*c2e18aaaSAndroid Build Coastguard Worker #[test]
test_shadow_apk_check_no_shadowing_apks() -> Result<()>828*c2e18aaaSAndroid Build Coastguard Worker fn test_shadow_apk_check_no_shadowing_apks() -> Result<()> {
829*c2e18aaaSAndroid Build Coastguard Worker let mut output = Vec::new();
830*c2e18aaaSAndroid Build Coastguard Worker let files = &HashMap::from([(PathBuf::from("/system/app1.apk"), PushState::Push)]);
831*c2e18aaaSAndroid Build Coastguard Worker let result = shadow_apk_check(&mut output, files);
832*c2e18aaaSAndroid Build Coastguard Worker
833*c2e18aaaSAndroid Build Coastguard Worker assert!(result.is_ok());
834*c2e18aaaSAndroid Build Coastguard Worker assert!(output.is_empty());
835*c2e18aaaSAndroid Build Coastguard Worker Ok(())
836*c2e18aaaSAndroid Build Coastguard Worker }
837*c2e18aaaSAndroid Build Coastguard Worker
838*c2e18aaaSAndroid Build Coastguard Worker #[test]
test_shadow_apk_check_with_shadowing_apks() -> Result<()>839*c2e18aaaSAndroid Build Coastguard Worker fn test_shadow_apk_check_with_shadowing_apks() -> Result<()> {
840*c2e18aaaSAndroid Build Coastguard Worker let mut output = Vec::new();
841*c2e18aaaSAndroid Build Coastguard Worker let files = &HashMap::from([
842*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("/system/app1.apk"), PushState::Push),
843*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("/data/app2.apk"), PushState::ApkInstalled),
844*c2e18aaaSAndroid Build Coastguard Worker (PathBuf::from("/data/app3.apk"), PushState::ApkInstalled),
845*c2e18aaaSAndroid Build Coastguard Worker ]);
846*c2e18aaaSAndroid Build Coastguard Worker let result = shadow_apk_check(&mut output, files);
847*c2e18aaaSAndroid Build Coastguard Worker assert!(result.is_err());
848*c2e18aaaSAndroid Build Coastguard Worker let output_str = String::from_utf8(output).unwrap();
849*c2e18aaaSAndroid Build Coastguard Worker assert!(
850*c2e18aaaSAndroid Build Coastguard Worker output_str.contains("Either `adb uninstall` these packages or `adb install` by hand.")
851*c2e18aaaSAndroid Build Coastguard Worker );
852*c2e18aaaSAndroid Build Coastguard Worker assert!(output_str.contains("adb uninstall /data/app2.apk;"));
853*c2e18aaaSAndroid Build Coastguard Worker assert!(output_str.contains("adb uninstall /data/app3.apk;"));
854*c2e18aaaSAndroid Build Coastguard Worker Ok(())
855*c2e18aaaSAndroid Build Coastguard Worker }
856*c2e18aaaSAndroid Build Coastguard Worker
857*c2e18aaaSAndroid Build Coastguard Worker #[test]
on_host_not_in_tracked_on_device() -> Result<()>858*c2e18aaaSAndroid Build Coastguard Worker fn on_host_not_in_tracked_on_device() -> Result<()> {
859*c2e18aaaSAndroid Build Coastguard Worker let results = call_update(&FakeState {
860*c2e18aaaSAndroid Build Coastguard Worker device_data: &["system/f1"],
861*c2e18aaaSAndroid Build Coastguard Worker host_data: &["system/f1"],
862*c2e18aaaSAndroid Build Coastguard Worker tracked_set: &[],
863*c2e18aaaSAndroid Build Coastguard Worker })?
864*c2e18aaaSAndroid Build Coastguard Worker .upserts;
865*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(0, results.values().len());
866*c2e18aaaSAndroid Build Coastguard Worker Ok(())
867*c2e18aaaSAndroid Build Coastguard Worker }
868*c2e18aaaSAndroid Build Coastguard Worker
869*c2e18aaaSAndroid Build Coastguard Worker #[test]
in_host_not_in_tracked_not_on_device() -> Result<()>870*c2e18aaaSAndroid Build Coastguard Worker fn in_host_not_in_tracked_not_on_device() -> Result<()> {
871*c2e18aaaSAndroid Build Coastguard Worker let results = call_update(&FakeState {
872*c2e18aaaSAndroid Build Coastguard Worker device_data: &[""],
873*c2e18aaaSAndroid Build Coastguard Worker host_data: &["system/f1"],
874*c2e18aaaSAndroid Build Coastguard Worker tracked_set: &[],
875*c2e18aaaSAndroid Build Coastguard Worker })?
876*c2e18aaaSAndroid Build Coastguard Worker .upserts;
877*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(0, results.values().len());
878*c2e18aaaSAndroid Build Coastguard Worker Ok(())
879*c2e18aaaSAndroid Build Coastguard Worker }
880*c2e18aaaSAndroid Build Coastguard Worker
881*c2e18aaaSAndroid Build Coastguard Worker #[test]
test_parents_stops_at_partition()882*c2e18aaaSAndroid Build Coastguard Worker fn test_parents_stops_at_partition() {
883*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
884*c2e18aaaSAndroid Build Coastguard Worker vec![
885*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("some/long/path/file"),
886*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("some/long/path"),
887*c2e18aaaSAndroid Build Coastguard Worker PathBuf::from("some/long"),
888*c2e18aaaSAndroid Build Coastguard Worker ],
889*c2e18aaaSAndroid Build Coastguard Worker parents("some/long/path/file", &[PathBuf::from("some")]),
890*c2e18aaaSAndroid Build Coastguard Worker );
891*c2e18aaaSAndroid Build Coastguard Worker }
892*c2e18aaaSAndroid Build Coastguard Worker
893*c2e18aaaSAndroid Build Coastguard Worker #[test]
validate_partition_removes_unused_default_partition() -> Result<()>894*c2e18aaaSAndroid Build Coastguard Worker fn validate_partition_removes_unused_default_partition() -> Result<()> {
895*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = TempDir::new().unwrap();
896*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(tmp_root.path().join("system")).unwrap();
897*c2e18aaaSAndroid Build Coastguard Worker
898*c2e18aaaSAndroid Build Coastguard Worker // No system_ext here, so remove from default partitions
899*c2e18aaaSAndroid Build Coastguard Worker let ninja_deps = vec![
900*c2e18aaaSAndroid Build Coastguard Worker "system/file1".to_string(),
901*c2e18aaaSAndroid Build Coastguard Worker "file3".to_string(),
902*c2e18aaaSAndroid Build Coastguard Worker "system/dir2/file1".to_string(),
903*c2e18aaaSAndroid Build Coastguard Worker "data/sys/file4".to_string(),
904*c2e18aaaSAndroid Build Coastguard Worker ];
905*c2e18aaaSAndroid Build Coastguard Worker assert_eq!(
906*c2e18aaaSAndroid Build Coastguard Worker vec!["system".to_string(),],
907*c2e18aaaSAndroid Build Coastguard Worker validate_partitions(tmp_root.path(), &ninja_deps, &None)?
908*c2e18aaaSAndroid Build Coastguard Worker );
909*c2e18aaaSAndroid Build Coastguard Worker Ok(())
910*c2e18aaaSAndroid Build Coastguard Worker }
911*c2e18aaaSAndroid Build Coastguard Worker
912*c2e18aaaSAndroid Build Coastguard Worker #[test]
validate_partition_bails_on_bad_partition_name()913*c2e18aaaSAndroid Build Coastguard Worker fn validate_partition_bails_on_bad_partition_name() {
914*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = TempDir::new().unwrap();
915*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(tmp_root.path().join("system")).unwrap();
916*c2e18aaaSAndroid Build Coastguard Worker fs::create_dir_all(tmp_root.path().join("sys")).unwrap();
917*c2e18aaaSAndroid Build Coastguard Worker
918*c2e18aaaSAndroid Build Coastguard Worker let ninja_deps = vec![
919*c2e18aaaSAndroid Build Coastguard Worker "system/file1".to_string(),
920*c2e18aaaSAndroid Build Coastguard Worker "file3".to_string(),
921*c2e18aaaSAndroid Build Coastguard Worker "system/dir2/file1".to_string(),
922*c2e18aaaSAndroid Build Coastguard Worker "data/sys/file4".to_string(),
923*c2e18aaaSAndroid Build Coastguard Worker ];
924*c2e18aaaSAndroid Build Coastguard Worker // "sys" isn't a valid partition name, but it matches a prefix of "system".
925*c2e18aaaSAndroid Build Coastguard Worker // Should bail.
926*c2e18aaaSAndroid Build Coastguard Worker match validate_partitions(tmp_root.path(), &ninja_deps, &Some(vec!["sys".to_string()])) {
927*c2e18aaaSAndroid Build Coastguard Worker Ok(_) => panic!("Expected error"),
928*c2e18aaaSAndroid Build Coastguard Worker Err(e) => {
929*c2e18aaaSAndroid Build Coastguard Worker assert!(
930*c2e18aaaSAndroid Build Coastguard Worker e.to_string().contains("\"sys\" is not a valid partition"),
931*c2e18aaaSAndroid Build Coastguard Worker "{}",
932*c2e18aaaSAndroid Build Coastguard Worker e.to_string()
933*c2e18aaaSAndroid Build Coastguard Worker )
934*c2e18aaaSAndroid Build Coastguard Worker }
935*c2e18aaaSAndroid Build Coastguard Worker }
936*c2e18aaaSAndroid Build Coastguard Worker }
937*c2e18aaaSAndroid Build Coastguard Worker
938*c2e18aaaSAndroid Build Coastguard Worker #[test]
validate_partition_bails_on_no_partition_on_host()939*c2e18aaaSAndroid Build Coastguard Worker fn validate_partition_bails_on_no_partition_on_host() {
940*c2e18aaaSAndroid Build Coastguard Worker let tmp_root = TempDir::new().unwrap();
941*c2e18aaaSAndroid Build Coastguard Worker
942*c2e18aaaSAndroid Build Coastguard Worker let ninja_deps = vec!["system/file1".to_string()];
943*c2e18aaaSAndroid Build Coastguard Worker match validate_partitions(tmp_root.path(), &ninja_deps, &Some(vec!["system".to_string()])) {
944*c2e18aaaSAndroid Build Coastguard Worker Ok(_) => panic!("Expected error"),
945*c2e18aaaSAndroid Build Coastguard Worker Err(e) => {
946*c2e18aaaSAndroid Build Coastguard Worker assert!(
947*c2e18aaaSAndroid Build Coastguard Worker e.to_string().contains("\"system\" partition does not exist on host"),
948*c2e18aaaSAndroid Build Coastguard Worker "{}",
949*c2e18aaaSAndroid Build Coastguard Worker e.to_string()
950*c2e18aaaSAndroid Build Coastguard Worker )
951*c2e18aaaSAndroid Build Coastguard Worker }
952*c2e18aaaSAndroid Build Coastguard Worker }
953*c2e18aaaSAndroid Build Coastguard Worker }
954*c2e18aaaSAndroid Build Coastguard Worker
955*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Test case where on device and up to date, but not tracked.
956*c2e18aaaSAndroid Build Coastguard Worker
957*c2e18aaaSAndroid Build Coastguard Worker struct FakeState {
958*c2e18aaaSAndroid Build Coastguard Worker device_data: &'static [&'static str],
959*c2e18aaaSAndroid Build Coastguard Worker host_data: &'static [&'static str],
960*c2e18aaaSAndroid Build Coastguard Worker tracked_set: &'static [&'static str],
961*c2e18aaaSAndroid Build Coastguard Worker }
962*c2e18aaaSAndroid Build Coastguard Worker
963*c2e18aaaSAndroid Build Coastguard Worker // Helper to call update.
964*c2e18aaaSAndroid Build Coastguard Worker // Uses filename for the digest in the fingerprint
965*c2e18aaaSAndroid Build Coastguard Worker // Add directories for every file on the host like walkdir would do.
966*c2e18aaaSAndroid Build Coastguard Worker // `update` adds the directories for the tracked set so we don't do that here.
call_update(fake_state: &FakeState) -> Result<commands::Commands>967*c2e18aaaSAndroid Build Coastguard Worker fn call_update(fake_state: &FakeState) -> Result<commands::Commands> {
968*c2e18aaaSAndroid Build Coastguard Worker let product_out = PathBuf::from("");
969*c2e18aaaSAndroid Build Coastguard Worker let installed_apks = HashSet::<String>::new();
970*c2e18aaaSAndroid Build Coastguard Worker let partitions = Vec::new();
971*c2e18aaaSAndroid Build Coastguard Worker let force = false;
972*c2e18aaaSAndroid Build Coastguard Worker let mut device_files: HashMap<PathBuf, FileMetadata> = HashMap::new();
973*c2e18aaaSAndroid Build Coastguard Worker let mut host_files: HashMap<PathBuf, FileMetadata> = HashMap::new();
974*c2e18aaaSAndroid Build Coastguard Worker for d in fake_state.device_data {
975*c2e18aaaSAndroid Build Coastguard Worker // Set the digest to the filename for now.
976*c2e18aaaSAndroid Build Coastguard Worker device_files.insert(PathBuf::from(d), file_metadata(d));
977*c2e18aaaSAndroid Build Coastguard Worker }
978*c2e18aaaSAndroid Build Coastguard Worker for h in fake_state.host_data {
979*c2e18aaaSAndroid Build Coastguard Worker // Set the digest to the filename for now.
980*c2e18aaaSAndroid Build Coastguard Worker host_files.insert(PathBuf::from(h), file_metadata(h));
981*c2e18aaaSAndroid Build Coastguard Worker // Add the dir too.
982*c2e18aaaSAndroid Build Coastguard Worker }
983*c2e18aaaSAndroid Build Coastguard Worker
984*c2e18aaaSAndroid Build Coastguard Worker let tracked_set: Vec<String> =
985*c2e18aaaSAndroid Build Coastguard Worker fake_state.tracked_set.iter().map(|s| s.to_string()).collect();
986*c2e18aaaSAndroid Build Coastguard Worker
987*c2e18aaaSAndroid Build Coastguard Worker let mut stdout = Vec::new();
988*c2e18aaaSAndroid Build Coastguard Worker get_update_commands(
989*c2e18aaaSAndroid Build Coastguard Worker &device_files,
990*c2e18aaaSAndroid Build Coastguard Worker &host_files,
991*c2e18aaaSAndroid Build Coastguard Worker &tracked_set,
992*c2e18aaaSAndroid Build Coastguard Worker product_out,
993*c2e18aaaSAndroid Build Coastguard Worker &installed_apks,
994*c2e18aaaSAndroid Build Coastguard Worker DiffMode::UsePermissions,
995*c2e18aaaSAndroid Build Coastguard Worker &partitions,
996*c2e18aaaSAndroid Build Coastguard Worker force,
997*c2e18aaaSAndroid Build Coastguard Worker &mut stdout,
998*c2e18aaaSAndroid Build Coastguard Worker )
999*c2e18aaaSAndroid Build Coastguard Worker }
1000*c2e18aaaSAndroid Build Coastguard Worker
file_metadata(digest: &str) -> FileMetadata1001*c2e18aaaSAndroid Build Coastguard Worker fn file_metadata(digest: &str) -> FileMetadata {
1002*c2e18aaaSAndroid Build Coastguard Worker FileMetadata {
1003*c2e18aaaSAndroid Build Coastguard Worker file_type: fingerprint::FileType::File,
1004*c2e18aaaSAndroid Build Coastguard Worker digest: digest.to_string(),
1005*c2e18aaaSAndroid Build Coastguard Worker ..Default::default()
1006*c2e18aaaSAndroid Build Coastguard Worker }
1007*c2e18aaaSAndroid Build Coastguard Worker }
1008*c2e18aaaSAndroid Build Coastguard Worker
dir_metadata() -> FileMetadata1009*c2e18aaaSAndroid Build Coastguard Worker fn dir_metadata() -> FileMetadata {
1010*c2e18aaaSAndroid Build Coastguard Worker FileMetadata { file_type: fingerprint::FileType::Directory, ..Default::default() }
1011*c2e18aaaSAndroid Build Coastguard Worker }
1012*c2e18aaaSAndroid Build Coastguard Worker // TODO(rbraunstein): Add tests for collect_status_per_file after we decide on output.
1013*c2e18aaaSAndroid Build Coastguard Worker }
1014