adevice status working
Ties together the new adevice_fingerprint binary with host fingerprint
function to report diffs.
Reporting UX is not in the final form, but currently looks like:
% adevice status
Device Needs: 2
system/test_dir
system/test_dir/test_file
Device Diffs: 1
system/bin/adevice_fingerprint
Device Extra: 0
Test: atest
Test: mkdir -p $ANDROID_PRODUCT_OUT/system/test_dir && touch $ANDROID_PRODUCT_OUT/system/test_dir/test_file && adevice status
Change-Id: I7d46921387245e25732df931942a4f964fbcff9e
diff --git a/adevice/Android.bp b/adevice/Android.bp
index 4122130..0b38f7d 100644
--- a/adevice/Android.bp
+++ b/adevice/Android.bp
@@ -58,6 +58,7 @@
rust_defaults {
name: "adevice_deps_defaults",
rustlibs: [
+ "libanyhow",
"libatty",
"libclap",
"libhex",
diff --git a/adevice/src/adevice.rs b/adevice/src/adevice.rs
index 8cce2df..2e7c1f0 100644
--- a/adevice/src/adevice.rs
+++ b/adevice/src/adevice.rs
@@ -2,39 +2,55 @@
mod cli;
mod fingerprint;
+use anyhow::{anyhow, bail, Context, Result};
use clap::Parser;
use cli::Commands;
+use fingerprint::FileMetadata;
-use std::collections::HashMap;
-use std::io::{self, Write};
+use std::collections::{BTreeSet, HashMap};
+use std::io::BufReader;
use std::path::PathBuf;
+use std::process;
-// TODO(rbraunstein): Remove all allow(unused) when implementing functions.
-#[allow(unused)]
-fn main() {
- let adb = Adb {};
+fn main() -> Result<()> {
let cli = cli::Cli::parse();
let product_out = match &cli.global_options.product_out {
Some(po) => PathBuf::from(po),
- None => get_product_out_from_env()
- .ok_or("ANDROID_PRODUCT_OUT is not set. Please run source build/envsetup.sh and lunch.")
- .unwrap(),
+ None => get_product_out_from_env().ok_or(anyhow!(
+ "ANDROID_PRODUCT_OUT is not set. Please run source build/envsetup.sh and lunch."
+ ))?,
};
let partitions: Vec<PathBuf> =
cli.global_options.partitions.iter().map(PathBuf::from).collect();
- let device_tree = fingerprint_device(&partitions, &adb).unwrap();
- let build_tree = fingerprint::fingerprint_partitions(&product_out, &partitions).unwrap();
+ let device_tree = fingerprint_device(&cli.global_options.partitions)?;
+ let build_tree = fingerprint::fingerprint_partitions(&product_out, &partitions)?;
match &cli.command {
Commands::Status => {
let changes = fingerprint::diff(&build_tree, &device_tree);
- io::stdout().write_all(&format!("{changes:?}").into_bytes());
+ report_diffs("Device Needs", &changes.device_needs);
+ report_diffs("Device Diffs", &changes.device_diffs);
+ report_diffs("Device Extra", &changes.device_extra);
}
Commands::ShowActions => {}
+ #[allow(unused)]
Commands::UpdateDevice { verbose, reboot } => {}
}
+ Ok(())
+}
+
+fn report_diffs(category: &str, file_names: &HashMap<PathBuf, FileMetadata>) {
+ let sorted_names = BTreeSet::from_iter(file_names.keys());
+ println!("{category}: {} files.", sorted_names.len());
+
+ for (index, value) in sorted_names.iter().enumerate() {
+ if index > 10 {
+ break;
+ }
+ println!("\t{}", value.display());
+ }
}
fn get_product_out_from_env() -> Option<PathBuf> {
@@ -55,15 +71,28 @@
/// return an entry for each file found. The entry contains the
/// digest of the file contents and stat-like data about the file.
/// Typically, dirs = ["system"]
-#[allow(unused)]
fn fingerprint_device(
- partitions: &[PathBuf],
- adb: &Adb,
-) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>, String> {
- // Call our helper binary running on device, return errors if we can't contact it.
- Err("use adb hashdevice command in upcoming PR".to_string())
-}
-
-struct Adb {
- // Just a placeholder for now.
+ partitions: &[String],
+) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>> {
+ let mut args =
+ vec!["shell".to_string(), "/system/bin/adevice_fingerprint".to_string(), "-p".to_string()];
+ args.extend_from_slice(partitions);
+ let mut adb = process::Command::new("adb")
+ .args(args)
+ .stdout(process::Stdio::piped())
+ .spawn()
+ .context("Error running adb")?;
+ let stdout = adb.stdout.take().ok_or(anyhow!("Unable to read stdout from adb"))?;
+ let result: HashMap<String, fingerprint::FileMetadata> =
+ match serde_json::from_reader(BufReader::new(stdout)) {
+ Err(err) if err.line() == 1 && err.column() == 0 && err.is_eof() => {
+ // This means there was no data. Print a different error, and adb
+ // probably also just printed a line.
+ bail!("Device didn't return any data.");
+ }
+ Err(err) => bail!("Error reading json {}", err),
+ Ok(file_map) => file_map,
+ };
+ adb.wait()?;
+ Ok(result.into_iter().map(|(path, metadata)| (PathBuf::from(path), metadata)).collect())
}
diff --git a/adevice/src/adevice_fingerprint.rs b/adevice/src/adevice_fingerprint.rs
index 6c9a6a4..213fd5d 100644
--- a/adevice/src/adevice_fingerprint.rs
+++ b/adevice/src/adevice_fingerprint.rs
@@ -1,8 +1,9 @@
//! Tool to fingerprint the files on the device's filesystem.
// Run with:
-// m adevice_helper
-// adb push $ANDROID_PRODUCT_OUT/system/bin/adevice_helper /data/bin/adevice_helper
-// adb shell /data/bin/adevice_helper --partitions system
+// adb root
+// m adevice_fingerprint
+// adb push $ANDROID_PRODUCT_OUT/system/bin/adevice_fingerprint /system/bin/adevice_fingerprint
+// adb shell /system/bin/adevice_fingerprint --partitions system
use clap::Parser;
use std::io;
diff --git a/adevice/src/fingerprint.rs b/adevice/src/fingerprint.rs
index a8b388b..f8dadf1 100644
--- a/adevice/src/fingerprint.rs
+++ b/adevice/src/fingerprint.rs
@@ -54,7 +54,7 @@
/// Each file should land in of the three categories (i.e. updated, not
/// removed and added);
/// TODO(rbraunstein): Fix allow(unused) by breaking out methods not
-/// needed by adevice_helper.
+/// needed by adevice_fingerprint.
#[allow(unused)]
pub fn diff(
host_files: &HashMap<PathBuf, FileMetadata>,
@@ -69,15 +69,13 @@
// Files on the host, but not on the device or
// file on the host that are different on the device.
for (file_name, metadata) in host_files {
- // TODO(rbraunstein): Can I make the logic clearer without nesting?
- if let Some(device_metadata) = device_files.get(file_name) {
- if device_metadata != metadata {
- diffs.device_diffs.insert(file_name.clone(), metadata.clone());
- } else {
- // NO diff, nothing to do.
- };
- } else {
- diffs.device_needs.insert(file_name.clone(), metadata.clone());
+ match device_files.get(file_name) {
+ Some(device_metadata) if device_metadata != metadata => {
+ diffs.device_diffs.insert(file_name.clone(), metadata.clone())
+ }
+ // If the device metadata == host metadata there is nothing to do.
+ Some(_) => None,
+ None => diffs.device_needs.insert(file_name.clone(), metadata.clone()),
};
}