Repository: flutter-rs/cargo-flutter (language: rust)

Issue:
support AOT builds
- https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode
- https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedding-in-AOT-Mode
- https://github.com/flutter/flutter/issues/44577#issuecomment-553149711
replace unzip by using zip-rs directly
unzip doesn't preserve unix file permissions


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 828b74d..c382e7b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -206,8 +206,8 @@ dependencies = [
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
  "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "unzip 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "which 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -736,15 +736,6 @@ dependencies = [
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "msdos_time"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "num_cpus"
 version = "1.11.1"
@@ -1188,11 +1179,6 @@ dependencies = [
  "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "transformation-pipeline"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "typenum"
 version = "1.11.2"
@@ -1224,16 +1210,6 @@ name = "unicode-xid"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "unzip"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "transformation-pipeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "zip 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "url"
 version = "2.1.0"
@@ -1348,12 +1324,12 @@ dependencies = [
 
 [[package]]
 name = "zip"
-version = "0.2.8"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "msdos_time 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1437,7 +1413,6 @@ dependencies = [
 "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
 "checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625"
 "checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
-"checksum msdos_time 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729"
 "checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
 "checksum opener 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13117407ca9d0caf3a0e74f97b490a7e64c0ae3aa90a8b7085544d0c37b6f3ae"
 "checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585"
@@ -1493,13 +1468,11 @@ dependencies = [
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
 "checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf"
-"checksum transformation-pipeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce1d17c634c5196b37a2680f31627ab35b9e84e5f7f1abd13731c3fbd71dea36"
 "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
 "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
 "checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf"
 "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
 "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
-"checksum unzip 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "90e59a2baae95b4b60ecd5a1ba39e9654b09b09bf813989c70f52c957c213dfd"
 "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"
 "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d"
 "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
@@ -1516,4 +1489,4 @@ dependencies = [
 "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 "checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9"
-"checksum zip 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e7341988e4535c60882d5e5f0b7ad0a9a56b080ade8bdb5527cb512f7b2180e0"
+"checksum zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3c21bb410afa2bd823a047f5bda3adb62f51074ac7e06263b2c97ecdd47e9fc6"
diff --git a/Cargo.toml b/Cargo.toml
index f8e9fd6..41d2205 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,5 +15,5 @@ failure = "0.1.6"
 log = "0.4.8"
 serde = { version = "1.0.104", features = ["derive"] }
 toml = "0.5.5"
-unzip = "0.1.0"
 which = "3.1.0"
+zip = "0.5.3"
diff --git a/src/cargo.rs b/src/cargo.rs
index 18050bd..87b6107 100644
--- a/src/cargo.rs
+++ b/src/cargo.rs
@@ -111,7 +111,8 @@ impl<'a> Cargo<'a> {
     }
 
     pub fn run(&self, engine_path: &Path) -> Result<String, Error> {
-        let mut child = self.cargo_command(engine_path)
+        let mut child = self
+            .cargo_command(engine_path)
             .stdout(Stdio::piped())
             .spawn()
             .expect("Success");
diff --git a/src/engine.rs b/src/engine.rs
index c5eae26..1bb560f 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1,19 +1,18 @@
 use curl::easy::Easy;
 use std::fs::File;
-use std::io::{BufReader, Write};
+use std::io::Write;
 use std::path::PathBuf;
-use std::process::Command;
 use std::sync::{mpsc, Mutex};
 use std::{fs, thread};
 
-#[derive(Debug)]
+#[derive(Clone, Debug, Eq, PartialEq)]
 pub struct Engine {
     version: String,
     target: String,
     build: Build,
 }
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum Build {
     Debug,
     Release,
@@ -79,7 +78,6 @@ impl Engine {
                 .join("engine_out")
                 .join(self.library_name())
         };
-        log::info!("FLUTTER_ENGINE_PATH {}", path.display());
         path
     }
 
@@ -87,7 +85,6 @@ impl Engine {
         let url = self.download_url();
         let path = self.engine_path();
         let dir = path.parent().unwrap().parent().unwrap().to_owned();
-        let is_macos = self.target.contains("apple");
 
         if path.exists() {
             return;
@@ -127,30 +124,7 @@ impl Engine {
             println!("Download finished");
 
             println!("Extracting...");
-            let zip_file = File::open(&download_file).unwrap();
-            let reader = BufReader::new(zip_file);
-            let unzipper = unzip::Unzipper::new(reader, &dir);
-            unzipper.unzip().unwrap();
-
-            // mac framework file is a double zip file
-            if is_macos {
-                Command::new("unzip")
-                    .args(&[
-                        "FlutterEmbedder.framework.zip",
-                        "-d",
-                        "FlutterEmbedder.framework",
-                    ])
-                    .current_dir(&dir)
-                    .status()
-                    .unwrap();
-
-                // TODO: fixme
-                // unzip bug! Extracted file corrupted!
-                // let zip_file = File::open(dir.join("FlutterEmbedder.framework.zip")).unwrap();
-                // let reader = BufReader::new(zip_file);
-                // let unzipper = unzip::Unzipper::new(reader, dir.join("FlutterEmbedder.framework"));
-                // unzipper.unzip().unwrap();
-            }
+            crate::unzip::unzip(&download_file, &dir).unwrap();
         });
         for (total, done) in rx.iter() {
             println!("Downloading flutter engine {} of {}", done, total);
diff --git a/src/flutter.rs b/src/flutter.rs
index bb49125..66ea276 100644
--- a/src/flutter.rs
+++ b/src/flutter.rs
@@ -1,8 +1,7 @@
 use crate::cargo::Cargo;
 use crate::engine::Build;
 use crate::error::Error;
-use cargo::core::Workspace;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::process::Command;
 
 pub struct Flutter {
@@ -23,10 +22,13 @@ impl Flutter {
                 .ok_or(Error::FlutterNotFound)?
                 .to_owned()
         };
-        log::info!("FLUTTER_ROOT {}", root.display());
         Ok(Flutter { root })
     }
 
+    pub fn root(&self) -> &Path {
+        &self.root
+    }
+
     pub fn engine_version(&self) -> Result<String, Error> {
         let version = if let Ok(v) = std::env::var("FLUTTER_ENGINE_VERSION") {
             v
@@ -38,7 +40,6 @@ impl Flutter {
                 .join("engine.version");
             std::fs::read_to_string(path).map(|v| v.trim().to_owned())?
         };
-        log::info!("FLUTTER_ENGINE_VERSION {}", version);
         Ok(version)
     }
 
@@ -67,10 +68,10 @@ impl Flutter {
         Ok(())
     }
 
-    pub fn attach(&self, workspace: &Workspace, debug_uri: &str) -> Result<(), Error> {
+    pub fn attach(&self, cargo: &Cargo, debug_uri: &str) -> Result<(), Error> {
         let debug_uri = format!("--debug-uri={}", debug_uri);
         let status = Command::new("flutter")
-            .current_dir(workspace.root())
+            .current_dir(cargo.workspace().root())
             .arg("attach")
             .arg("--device-id=flutter-tester")
             .arg(debug_uri)
@@ -81,4 +82,44 @@ impl Flutter {
         }
         Ok(())
     }
+
+    pub fn aot(&self, cargo: &Cargo, engine_path: &Path) -> Result<(), Error> {
+        let root = cargo.workspace().root();
+        let build_dir = cargo.build_dir();
+        let engine_dir = engine_path.parent().unwrap();
+        let snapshot = build_dir.join("kernel_snapshot.dill");
+        let status = Command::new(engine_dir.join("dart"))
+            .current_dir(root)
+            .arg(engine_dir.join("gen").join("frontend_server.dart.snapshot"))
+            .arg("--sdk-root")
+            .arg(engine_dir.join("flutter_patched_sdk"))
+            .arg("--target=flutter")
+            .arg("--aot")
+            .arg("--tfa")
+            .arg("-Ddart.vm.product=true")
+            .arg("--packages")
+            .arg(".packages")
+            .arg("--output-dill")
+            .arg(&snapshot)
+            .arg(root.join("lib").join("main.dart"))
+            .status()
+            .expect("Success");
+        if status.code() != Some(0) {
+            return Err(Error::FlutterError);
+        }
+        let status = Command::new(engine_dir.join("gen_snapshot"))
+            .current_dir(root)
+            .arg("--causal_async_stacks")
+            .arg("--deterministic")
+            .arg("--snapshot_kind=app-aot-elf")
+            .arg("--strip")
+            .arg(format!("--elf={}", build_dir.join("app.so").display()))
+            .arg(&snapshot)
+            .status()
+            .expect("Success");
+        if status.code() != Some(0) {
+            return Err(Error::FlutterError);
+        }
+        Ok(())
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index d90b2a5..17e4909 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,7 @@ mod engine;
 mod error;
 mod flutter;
 pub mod package;
+mod unzip;
 
 pub use crate::cargo::Cargo;
 pub use crate::config::TomlConfig;
diff --git a/src/main.rs b/src/main.rs
index 2b65523..754d46c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -23,11 +23,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
                         .takes_value(true)
                         .help("Packaging format"),
                 )
+                .arg(
+                    Arg::with_name("no-flutter")
+                        .long("no-flutter")
+                        .help("shortcut for no-bundle, no-attach and no-aot"),
+                )
                 .arg(
                     Arg::with_name("no-bundle")
                         .long("no-bundle")
                         .help("Skips running flutter bundle"),
                 )
+                .arg(
+                    Arg::with_name("no-attach")
+                        .long("no-attach")
+                        .help("Skips attaching the flutter debugger"),
+                )
+                .arg(
+                    Arg::with_name("no-aot")
+                        .long("no-aot")
+                        .help("Skips creating aot blob"),
+                )
                 .arg(
                     Arg::with_name("cargo-args")
                         .value_name("CARGO_ARGS")
@@ -55,22 +70,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     } else {
         Build::Debug
     };
+    let aot = build == Build::Release;
     let config = TomlConfig::load(&cargo)?;
     let metadata = config.metadata();
     let flutter = Flutter::new()?;
     let engine_version = metadata
         .engine_version()
         .unwrap_or_else(|| flutter.engine_version().unwrap());
+
+    log::debug!("FLUTTER_ROOT {}", flutter.root().display());
+    log::debug!("FLUTTER_ENGINE_VERSION {}", engine_version);
+
     let engine = Engine::new(engine_version, cargo.triple()?, build);
+    let engine_path = engine.engine_path();
+    let flutter_asset_dir = cargo.build_dir().join("flutter_assets");
+    let snapshot_path = cargo.build_dir().join("app.so");
+
+    log::debug!("FLUTTER_ENGINE_PATH {}", engine_path.display());
+    log::debug!("FLUTTER_ASSET_DIR {}", flutter_asset_dir.display());
+
     engine.download();
 
-    if !matches.is_present("no-bundle") {
+    if !matches.is_present("no-flutter") && !matches.is_present("no-bundle") {
         println!("flutter build bundle");
         flutter.bundle(&cargo, build)?;
     }
 
-    let flutter_asset_dir = cargo.build_dir().join("flutter_assets");
-    let engine_path = engine.engine_path();
+    if !matches.is_present("no-flutter") && !matches.is_present("no-aot") {
+        if aot {
+            flutter.aot(&cargo, &engine_path)?;
+        }
+    }
 
     match cargo.cmd() {
         "build" => {
@@ -80,6 +110,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
                 let mut package = Package::new(&config.package.name);
                 package.add_bin(cargo.build_dir().join(&config.package.name));
                 package.add_lib(engine_path);
+                if aot {
+                    package.add_lib(snapshot_path);
+                }
                 package.add_asset(flutter_asset_dir);
                 match format {
                     "appimage" => {
@@ -91,10 +124,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
             }
         }
         "run" => {
+            std::env::set_var("FLUTTER_AOT_SNAPSHOT", &snapshot_path);
             std::env::set_var("FLUTTER_ASSET_DIR", &flutter_asset_dir);
-            let debug_uri = cargo.run(&engine.engine_path())?;
+            let debug_uri = cargo.run(&engine_path)?;
             log::info!("Observatory at {}", debug_uri);
-            flutter.attach(cargo.workspace(), &debug_uri)?;
+
+            if !matches.is_present("no-flutter") && !matches.is_present("no-attach") {
+                flutter.attach(&cargo, &debug_uri)?;
+            }
         }
         _ => cargo.build(&engine_path)?,
     }
diff --git a/src/package/appimage.rs b/src/package/appimage.rs
index d7f3e6a..01e520b 100644
--- a/src/package/appimage.rs
+++ b/src/package/appimage.rs
@@ -93,6 +93,7 @@ HERE=${SELF%/*}
 export PATH="${HERE}/usr/bin/${PATH:+:$PATH}"
 export LD_LIBRARY_PATH="${HERE}/usr/lib/:${LD_LIBRARY_PATH:+:$LDLIBRARY_PATH}"
 export FLUTTER_ASSET_DIR="${HERE}/usr/share/flutter_assets"
+export FLUTTER_AOT_SNAPSHOT="${HERE}/usr/lib/app.so"
 EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
 exec "${EXEC}" "$@"
 "#;
diff --git a/src/unzip.rs b/src/unzip.rs
new file mode 100644
index 0000000..3d06e89
--- /dev/null
+++ b/src/unzip.rs
@@ -0,0 +1,43 @@
+use std::fs::File;
+use std::io::Error;
+use std::path::Path;
+use zip::ZipArchive;
+
+pub fn unzip(archive: &Path, dir: &Path) -> Result<(), Error> {
+    let file = File::open(archive)?;
+    let mut archive = ZipArchive::new(file)?;
+    for i in 0..archive.len() {
+        let mut file = archive.by_index(i)?;
+        let outpath = dir.join(file.sanitized_name());
+        if (&*file.name()).ends_with('/') {
+            println!("File {} extracted to \"{}\"", i, outpath.display());
+            std::fs::create_dir_all(&outpath)?;
+        } else {
+            println!(
+                "File {} extracted to \"{}\" ({} bytes)",
+                i,
+                outpath.display(),
+                file.size()
+            );
+            if let Some(p) = outpath.parent() {
+                if !p.exists() {
+                    std::fs::create_dir_all(&p)?;
+                }
+            }
+            let mut outfile = std::fs::File::create(&outpath)?;
+            std::io::copy(&mut file, &mut outfile).unwrap();
+
+            #[cfg(unix)]
+            {
+                use std::fs::Permissions;
+                use std::os::unix::fs::PermissionsExt;
+
+                if let Some(mode) = file.unix_mode() {
+                    std::fs::set_permissions(&outpath, Permissions::from_mode(mode))?;
+                }
+            }
+        }
+    }
+
+    Ok(())
+}


Test patch: