Reuse Android builds and discard staging
This commit is contained in:
1 parent
ea2112c552
commit
d1da066547
5 files changed
+75
-129
No files matched your search
+72
-15
@@ -173,7 +173,9 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
.join(variant)
|
.join(variant)
|
||||||
.join(&options.abi);
|
.join(&options.abi);
|
||||||
recreate(&output)?;
|
recreate(&output)?;
|
||||||
let native = output.join("native");
|
let staging = output.join("staging");
|
||||||
|
let staging_cleanup = RemoveDirOnDrop(&staging);
|
||||||
|
let native = staging.join("native");
|
||||||
let (build_manifest, library_name) = if options.example.is_some() {
|
let (build_manifest, library_name) = if options.example.is_some() {
|
||||||
let wrapper = metadata
|
let wrapper = metadata
|
||||||
.target_directory
|
.target_directory
|
||||||
@@ -213,9 +215,9 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
if !library.is_file() {
|
if !library.is_file() {
|
||||||
return Err(format!("cargo-ndk did not produce {}", library.display()));
|
return Err(format!("cargo-ndk did not produce {}", library.display()));
|
||||||
}
|
}
|
||||||
let classes = output.join("classes");
|
let classes = staging.join("classes");
|
||||||
fs::create_dir_all(&classes).map_err(io_error("create Java output", &classes))?;
|
fs::create_dir_all(&classes).map_err(io_error("create Java output", &classes))?;
|
||||||
let sources = materialize_host(&output, &library_name)?;
|
let sources = materialize_host(&staging, &library_name)?;
|
||||||
let java_files = files_with_extension(&sources, "java")?;
|
let java_files = files_with_extension(&sources, "java")?;
|
||||||
let mut javac = Command::new("javac");
|
let mut javac = Command::new("javac");
|
||||||
javac
|
javac
|
||||||
@@ -230,7 +232,7 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
"install a JDK containing javac",
|
"install a JDK containing javac",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let dex = output.join("dex");
|
let dex = staging.join("dex");
|
||||||
fs::create_dir_all(&dex).map_err(io_error("create DEX output", &dex))?;
|
fs::create_dir_all(&dex).map_err(io_error("create DEX output", &dex))?;
|
||||||
let class_files = files_with_extension(&classes, "class")?;
|
let class_files = files_with_extension(&classes, "class")?;
|
||||||
let mut d8 = Command::new(&sdk.d8);
|
let mut d8 = Command::new(&sdk.d8);
|
||||||
@@ -250,13 +252,13 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
"install Android SDK Build Tools",
|
"install Android SDK Build Tools",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let manifest = output.join("AndroidManifest.xml");
|
let manifest = staging.join("AndroidManifest.xml");
|
||||||
fs::write(
|
fs::write(
|
||||||
&manifest,
|
&manifest,
|
||||||
manifest_xml(&application_id, &label, &library_name, sdk.api),
|
manifest_xml(&application_id, &label, &library_name, sdk.api),
|
||||||
)
|
)
|
||||||
.map_err(io_error("write Android manifest", &manifest))?;
|
.map_err(io_error("write Android manifest", &manifest))?;
|
||||||
let unsigned = output.join("unsigned.apk");
|
let unsigned = staging.join("unsigned.apk");
|
||||||
let mut aapt = Command::new(&sdk.aapt2);
|
let mut aapt = Command::new(&sdk.aapt2);
|
||||||
aapt.arg("link")
|
aapt.arg("link")
|
||||||
.arg("-o")
|
.arg("-o")
|
||||||
@@ -278,14 +280,14 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
)?;
|
)?;
|
||||||
append_payload(
|
append_payload(
|
||||||
&unsigned,
|
&unsigned,
|
||||||
&output,
|
&staging,
|
||||||
&dex.join("classes.dex"),
|
&dex.join("classes.dex"),
|
||||||
&library,
|
&library,
|
||||||
&options.abi,
|
&options.abi,
|
||||||
&library_name,
|
&library_name,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let aligned = output.join("aligned.apk");
|
let aligned = staging.join("aligned.apk");
|
||||||
let mut zipalign = Command::new(&sdk.zipalign);
|
let mut zipalign = Command::new(&sdk.zipalign);
|
||||||
zipalign
|
zipalign
|
||||||
.args(["-P", "16", "-f", "4"])
|
.args(["-P", "16", "-f", "4"])
|
||||||
@@ -299,6 +301,8 @@ fn build(options: &Options) -> Result<Built, String> {
|
|||||||
let apk = output.join(format!("{artifact}-{variant}.apk"));
|
let apk = output.join(format!("{artifact}-{variant}.apk"));
|
||||||
sign(&sdk, options, &aligned, &apk)?;
|
sign(&sdk, options, &aligned, &apk)?;
|
||||||
verify(&sdk, &apk)?;
|
verify(&sdk, &apk)?;
|
||||||
|
fs::remove_dir_all(&staging).map_err(io_error("remove APK staging directory", &staging))?;
|
||||||
|
std::mem::forget(staging_cleanup);
|
||||||
Ok(Built {
|
Ok(Built {
|
||||||
apk,
|
apk,
|
||||||
application_id,
|
application_id,
|
||||||
@@ -405,23 +409,33 @@ fn materialize_example_wrapper(
|
|||||||
let manifest = format!(
|
let manifest = format!(
|
||||||
"[package]\nname = {name:?}\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\
|
"[package]\nname = {name:?}\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\
|
||||||
[lib]\nname = {library_name:?}\ncrate-type = [\"cdylib\", \"rlib\"]\n\n\
|
[lib]\nname = {library_name:?}\ncrate-type = [\"cdylib\", \"rlib\"]\n\n\
|
||||||
[dependencies]\niris = {{ path = {iris_root:?} }}\n\n[workspace]\n",
|
[dependencies]\niris = {{ path = {iris_root:?} }}\n\n[workspace]\n\n\
|
||||||
|
[profile.dev]\ndebug = \"line-tables-only\"\n",
|
||||||
name = format!("iris-android-{}-{}", package.name, example.name),
|
name = format!("iris-android-{}-{}", package.name, example.name),
|
||||||
iris_root = iris_root.as_str(),
|
iris_root = iris_root.as_str(),
|
||||||
);
|
);
|
||||||
let manifest_path = wrapper.join("Cargo.toml");
|
let manifest_path = wrapper.join("Cargo.toml");
|
||||||
fs::write(&manifest_path, manifest)
|
write_if_changed(&manifest_path, &manifest)?;
|
||||||
.map_err(io_error("write Android example manifest", &manifest_path))?;
|
|
||||||
let source = format!(
|
let source = format!(
|
||||||
"#[path = {:?}]\nmod example;\n",
|
"#[path = {:?}]\nmod example;\n",
|
||||||
android_source.to_string_lossy()
|
android_source.to_string_lossy()
|
||||||
);
|
);
|
||||||
let source_path = source_dir.join("lib.rs");
|
let source_path = source_dir.join("lib.rs");
|
||||||
fs::write(&source_path, source)
|
write_if_changed(&source_path, &source)?;
|
||||||
.map_err(io_error("write Android example wrapper", &source_path))?;
|
|
||||||
Ok((manifest_path, library_name))
|
Ok((manifest_path, library_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_if_changed(path: &Path, contents: &str) -> Result<bool, String> {
|
||||||
|
match fs::read(path) {
|
||||||
|
Ok(existing) if existing == contents.as_bytes() => return Ok(false),
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
|
Err(error) => return Err(format!("cannot read {}: {error}", path.display())),
|
||||||
|
}
|
||||||
|
fs::write(path, contents).map_err(io_error("write generated file", path))?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata_string(package: &Package, key: &str) -> Option<String> {
|
fn metadata_string(package: &Package, key: &str) -> Option<String> {
|
||||||
package
|
package
|
||||||
.metadata
|
.metadata
|
||||||
@@ -481,6 +495,16 @@ struct Sdk {
|
|||||||
adb: PathBuf,
|
adb: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Failed builds have no reusable staging output either; the next invocation
|
||||||
|
/// starts from scratch, so do not make a failure consume disk indefinitely.
|
||||||
|
struct RemoveDirOnDrop<'a>(&'a Path);
|
||||||
|
|
||||||
|
impl Drop for RemoveDirOnDrop<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = fs::remove_dir_all(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Sdk {
|
impl Sdk {
|
||||||
fn find() -> Result<Self, String> {
|
fn find() -> Result<Self, String> {
|
||||||
let root = env::var_os("ANDROID_HOME")
|
let root = env::var_os("ANDROID_HOME")
|
||||||
@@ -700,9 +724,13 @@ fn sign(sdk: &Sdk, options: &Options, input: &Path, output: &Path) -> Result<(),
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
let mut command = Command::new(&sdk.apksigner);
|
let mut command = Command::new(&sdk.apksigner);
|
||||||
|
// `install_and_run` uses `--no-streaming`, so it cannot consume the separate
|
||||||
|
// v4 `.idsig` file and retaining that sidecar beside the APK serves no caller.
|
||||||
command
|
command
|
||||||
.arg("sign")
|
.arg("sign")
|
||||||
.args([
|
.args([
|
||||||
|
"--v4-signing-enabled",
|
||||||
|
"false",
|
||||||
"--ks-pass",
|
"--ks-pass",
|
||||||
"env:IRIS_APK_STORE_PASSWORD",
|
"env:IRIS_APK_STORE_PASSWORD",
|
||||||
"--key-pass",
|
"--key-pass",
|
||||||
@@ -799,9 +827,9 @@ fn install_and_run(built: &Built, device: &str) -> Result<(), String> {
|
|||||||
|
|
||||||
fn recreate(path: &Path) -> Result<(), String> {
|
fn recreate(path: &Path) -> Result<(), String> {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
fs::remove_dir_all(path).map_err(io_error("clear prior APK staging directory", path))?;
|
fs::remove_dir_all(path).map_err(io_error("clear prior APK output directory", path))?;
|
||||||
}
|
}
|
||||||
fs::create_dir_all(path).map_err(io_error("create APK staging directory", path))
|
fs::create_dir_all(path).map_err(io_error("create APK output directory", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_command(command: &mut Command, thing: &str, fix: &str) -> Result<(), String> {
|
fn run_command(command: &mut Command, thing: &str, fix: &str) -> Result<(), String> {
|
||||||
@@ -842,4 +870,33 @@ mod tests {
|
|||||||
assert!(manifest.contains("A & <demo>"));
|
assert!(manifest.contains("A & <demo>"));
|
||||||
assert!(manifest.contains("android:minSdkVersion=\"29\""));
|
assert!(manifest.contains("android:minSdkVersion=\"29\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_unchanged_generated_file_is_not_rewritten() {
|
||||||
|
let root = env::temp_dir().join(format!("cargo-iris-write-test-{}", std::process::id()));
|
||||||
|
let path = root.join("generated.rs");
|
||||||
|
let _ = fs::remove_dir_all(&root);
|
||||||
|
fs::create_dir_all(&root).unwrap();
|
||||||
|
|
||||||
|
assert!(write_if_changed(&path, "first").unwrap());
|
||||||
|
assert!(!write_if_changed(&path, "first").unwrap());
|
||||||
|
assert!(write_if_changed(&path, "second").unwrap());
|
||||||
|
assert_eq!(fs::read_to_string(&path).unwrap(), "second");
|
||||||
|
|
||||||
|
fs::remove_dir_all(root).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_build_staging_is_removed_when_its_scope_ends() {
|
||||||
|
let root = env::temp_dir().join(format!("cargo-iris-staging-test-{}", std::process::id()));
|
||||||
|
let _ = fs::remove_dir_all(&root);
|
||||||
|
fs::create_dir_all(&root).unwrap();
|
||||||
|
fs::write(root.join("intermediate"), "not reusable").unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let _cleanup = RemoveDirOnDrop(&root);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!root.exists());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/Cargo.lock
|
|
||||||
/target/
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "iris-apk-fixture"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
iris = { path = "../../.." }
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
use iris::prelude::*;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(AndroidUiState)]
|
|
||||||
pub struct State {
|
|
||||||
ui_state: AndroidUiState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidAppState for State {
|
|
||||||
type Resources = Resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[iris::android_init]
|
|
||||||
fn create(mut ui_state: AndroidUiState, rsc: &mut Resources) -> State {
|
|
||||||
rsc.launches += 1;
|
|
||||||
rect(PaintId::RED)
|
|
||||||
.label("Iris APK fixture")
|
|
||||||
.set_root(rsc, &mut ui_state);
|
|
||||||
State { ui_state }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Resources {
|
|
||||||
ui: Ui,
|
|
||||||
events: EventManager<Self>,
|
|
||||||
tasks: Tasks<Self>,
|
|
||||||
widget_state: WidgetState,
|
|
||||||
launches: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AndroidResources<State> for Resources {
|
|
||||||
fn new(wake: Arc<dyn WakeTaskQueue>) -> (Self, TaskMsgReceiver<Self>) {
|
|
||||||
let (tasks, receiver) = Tasks::init(wake);
|
|
||||||
(
|
|
||||||
Self {
|
|
||||||
ui: Ui::default(),
|
|
||||||
events: EventManager::default(),
|
|
||||||
tasks,
|
|
||||||
widget_state: WidgetState::default(),
|
|
||||||
launches: 0,
|
|
||||||
},
|
|
||||||
receiver,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiRsc for Resources {
|
|
||||||
fn ui(&self) -> &Ui {
|
|
||||||
&self.ui
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui_mut(&mut self) -> &mut Ui {
|
|
||||||
&mut self.ui
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_draw(&mut self, active: &ActiveData) {
|
|
||||||
self.events.draw(active);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_undraw(&mut self, active: &ActiveData) {
|
|
||||||
self.events.undraw(active);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_remove(&mut self, id: WidgetId) {
|
|
||||||
self.events.remove(id);
|
|
||||||
self.widget_state.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasState for Resources {
|
|
||||||
type State = State;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasEvents for Resources {
|
|
||||||
fn events(&self) -> &EventManager<Self> {
|
|
||||||
&self.events
|
|
||||||
}
|
|
||||||
|
|
||||||
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
|
||||||
&mut self.events
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasTasks for Resources {
|
|
||||||
fn tasks_mut(&mut self) -> &mut Tasks<Self> {
|
|
||||||
&mut self.tasks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasWidgetState for Resources {
|
|
||||||
fn widget_state(&self) -> &WidgetState {
|
|
||||||
&self.widget_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn widget_state_mut(&mut self) -> &mut WidgetState {
|
|
||||||
&mut self.widget_state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -101,9 +101,9 @@ IRIS_KEYSTORE_PASSWORD=... IRIS_KEY_PASSWORD=... \
|
|||||||
cargo iris apk --release --keystore /secure/upload.jks --key-alias upload
|
cargo iris apk --release --keystore /secure/upload.jks --key-alias upload
|
||||||
```
|
```
|
||||||
|
|
||||||
APK staging and output live under
|
The verified APK lives under
|
||||||
`target/iris-android/<package>[-<example>]/<debug|release>/<abi>/`; the
|
`target/iris-android/<package>[-<example>]/<debug|release>/<abi>/`; packaging
|
||||||
command's final line is the verified APK's absolute path.
|
intermediates are removed before the command prints its absolute path.
|
||||||
|
|
||||||
Goals, in general order:
|
Goals, in general order:
|
||||||
1. does what I want it to (text, images, video, animations)
|
1. does what I want it to (text, images, video, animations)
|
||||||
|
|||||||
Reference in new issue
Block a user