Reuse Android builds and discard staging
This commit is contained in:
1 parent
779f5c63d3
commit
f7e7950908
5 files changed
+75
-129
No files matched your search
@@ -173,7 +173,9 @@ fn build(options: &Options) -> Result<Built, String> {
|
||||
.join(variant)
|
||||
.join(&options.abi);
|
||||
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 wrapper = metadata
|
||||
.target_directory
|
||||
@@ -213,9 +215,9 @@ fn build(options: &Options) -> Result<Built, String> {
|
||||
if !library.is_file() {
|
||||
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))?;
|
||||
let sources = materialize_host(&output, &library_name)?;
|
||||
let sources = materialize_host(&staging, &library_name)?;
|
||||
let java_files = files_with_extension(&sources, "java")?;
|
||||
let mut javac = Command::new("javac");
|
||||
javac
|
||||
@@ -230,7 +232,7 @@ fn build(options: &Options) -> Result<Built, String> {
|
||||
"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))?;
|
||||
let class_files = files_with_extension(&classes, "class")?;
|
||||
let mut d8 = Command::new(&sdk.d8);
|
||||
@@ -250,13 +252,13 @@ fn build(options: &Options) -> Result<Built, String> {
|
||||
"install Android SDK Build Tools",
|
||||
)?;
|
||||
|
||||
let manifest = output.join("AndroidManifest.xml");
|
||||
let manifest = staging.join("AndroidManifest.xml");
|
||||
fs::write(
|
||||
&manifest,
|
||||
manifest_xml(&application_id, &label, &library_name, sdk.api),
|
||||
)
|
||||
.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);
|
||||
aapt.arg("link")
|
||||
.arg("-o")
|
||||
@@ -278,14 +280,14 @@ fn build(options: &Options) -> Result<Built, String> {
|
||||
)?;
|
||||
append_payload(
|
||||
&unsigned,
|
||||
&output,
|
||||
&staging,
|
||||
&dex.join("classes.dex"),
|
||||
&library,
|
||||
&options.abi,
|
||||
&library_name,
|
||||
)?;
|
||||
|
||||
let aligned = output.join("aligned.apk");
|
||||
let aligned = staging.join("aligned.apk");
|
||||
let mut zipalign = Command::new(&sdk.zipalign);
|
||||
zipalign
|
||||
.args(["-P", "16", "-f", "4"])
|
||||
@@ -299,6 +301,8 @@ fn build(options: &Options) -> Result<Built, String> {
|
||||
let apk = output.join(format!("{artifact}-{variant}.apk"));
|
||||
sign(&sdk, options, &aligned, &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 {
|
||||
apk,
|
||||
application_id,
|
||||
@@ -405,23 +409,33 @@ fn materialize_example_wrapper(
|
||||
let manifest = format!(
|
||||
"[package]\nname = {name:?}\nversion = \"0.0.0\"\nedition = \"2024\"\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),
|
||||
iris_root = iris_root.as_str(),
|
||||
);
|
||||
let manifest_path = wrapper.join("Cargo.toml");
|
||||
fs::write(&manifest_path, manifest)
|
||||
.map_err(io_error("write Android example manifest", &manifest_path))?;
|
||||
write_if_changed(&manifest_path, &manifest)?;
|
||||
let source = format!(
|
||||
"#[path = {:?}]\nmod example;\n",
|
||||
android_source.to_string_lossy()
|
||||
);
|
||||
let source_path = source_dir.join("lib.rs");
|
||||
fs::write(&source_path, source)
|
||||
.map_err(io_error("write Android example wrapper", &source_path))?;
|
||||
write_if_changed(&source_path, &source)?;
|
||||
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> {
|
||||
package
|
||||
.metadata
|
||||
@@ -481,6 +495,16 @@ struct Sdk {
|
||||
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 {
|
||||
fn find() -> Result<Self, String> {
|
||||
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);
|
||||
// `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
|
||||
.arg("sign")
|
||||
.args([
|
||||
"--v4-signing-enabled",
|
||||
"false",
|
||||
"--ks-pass",
|
||||
"env:IRIS_APK_STORE_PASSWORD",
|
||||
"--key-pass",
|
||||
@@ -799,9 +827,9 @@ fn install_and_run(built: &Built, device: &str) -> Result<(), String> {
|
||||
|
||||
fn recreate(path: &Path) -> Result<(), String> {
|
||||
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> {
|
||||
@@ -842,4 +870,33 @@ mod tests {
|
||||
assert!(manifest.contains("A & <demo>"));
|
||||
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
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -101,9 +101,9 @@ IRIS_KEYSTORE_PASSWORD=... IRIS_KEY_PASSWORD=... \
|
||||
cargo iris apk --release --keystore /secure/upload.jks --key-alias upload
|
||||
```
|
||||
|
||||
APK staging and output live under
|
||||
`target/iris-android/<package>[-<example>]/<debug|release>/<abi>/`; the
|
||||
command's final line is the verified APK's absolute path.
|
||||
The verified APK lives under
|
||||
`target/iris-android/<package>[-<example>]/<debug|release>/<abi>/`; packaging
|
||||
intermediates are removed before the command prints its absolute path.
|
||||
|
||||
Goals, in general order:
|
||||
1. does what I want it to (text, images, video, animations)
|
||||
|
||||
Reference in new issue
Block a user