From 195094f415e27ec841bbca99885cc8292a85f2d3 Mon Sep 17 00:00:00 2001 From: Haihan Jiang Date: Tue, 26 May 2026 11:35:55 -0700 Subject: [PATCH] Warn about missing manifest versionCode --- Cargo.lock | 1 + pack-cli/Cargo.toml | 1 + pack-cli/src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 62adc8b..b8cbb93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,6 +680,7 @@ name = "pack-cli" version = "0.1.0" dependencies = [ "pack-api", + "xml", ] [[package]] diff --git a/pack-cli/Cargo.toml b/pack-cli/Cargo.toml index 49f2219..38d9626 100644 --- a/pack-cli/Cargo.toml +++ b/pack-cli/Cargo.toml @@ -5,3 +5,4 @@ edition = "2021" [dependencies] pack-api = { path = "../pack-api", features = ["cert-gen"] } +xml = "0.8.20" diff --git a/pack-cli/src/main.rs b/pack-cli/src/main.rs index 22b8b4b..2604827 100644 --- a/pack-cli/src/main.rs +++ b/pack-cli/src/main.rs @@ -14,11 +14,17 @@ use pack_api::{compile_and_sign_aab, compile_and_sign_apk, Keys, PackError, Package, Result}; use res_dir::read_res_dir; +use std::io::Cursor; use std::path::PathBuf; use std::{env, fs}; +use xml::reader::{EventReader, XmlEvent}; pub mod res_dir; +const ANDROID_NAMESPACE: &str = "http://schemas.android.com/apk/res/android"; +const MISSING_VERSION_CODE_WARNING: &str = "AndroidManifest.xml is missing android:versionCode \ + on its element. Android requires a version code for installable APKs."; + /// Run from a watch face directory to build signed APK and AAB files. /// /// ``` @@ -70,6 +76,10 @@ fn pack_main() -> Result<()> { let android_manifest = fs::read(&in_path)?; in_path.pop(); + for warning in critical_manifest_warnings(&android_manifest)? { + eprintln!("Warning: {warning}"); + } + in_path.push("res"); let resources = read_res_dir(&in_path)?; in_path.pop(); @@ -90,3 +100,65 @@ fn pack_main() -> Result<()> { Ok(()) } + +fn critical_manifest_warnings(manifest: &[u8]) -> Result> { + let parser = EventReader::new(Cursor::new(manifest)); + for event in parser { + match event.map_err(PackError::XmlParsingFailed)? { + XmlEvent::StartElement { + name, attributes, .. + } if name.local_name == "manifest" => { + let has_version_code = attributes.iter().any(|attr| { + attr.name.local_name == "versionCode" + && attr.name.namespace.as_deref() == Some(ANDROID_NAMESPACE) + }); + return Ok(if has_version_code { + vec![] + } else { + vec![MISSING_VERSION_CODE_WARNING] + }); + } + XmlEvent::StartElement { .. } => return Ok(vec![]), + _ => {} + } + } + Ok(vec![]) +} + +#[cfg(test)] +mod tests { + use super::*; + + const MANIFEST_WITH_VERSION_CODE: &[u8] = br#" + + + + "#; + + const MANIFEST_WITHOUT_VERSION_CODE: &[u8] = br#" + + + + "#; + + #[test] + fn does_not_warn_when_version_code_is_present() { + assert_eq!( + critical_manifest_warnings(MANIFEST_WITH_VERSION_CODE).unwrap(), + Vec::<&'static str>::new() + ); + } + + #[test] + fn warns_when_version_code_is_missing() { + assert_eq!( + critical_manifest_warnings(MANIFEST_WITHOUT_VERSION_CODE).unwrap(), + vec![MISSING_VERSION_CODE_WARNING] + ); + } +}