cargo_flutter/
flutter.rs

1use crate::cargo::Cargo;
2use crate::engine::{Build, Engine};
3use crate::error::Error;
4use std::path::{Path, PathBuf};
5use std::process::Command;
6
7pub struct Flutter {
8    root: PathBuf,
9}
10
11impl Flutter {
12    pub fn new() -> Result<Self, Error> {
13        let root = if let Ok(root) = std::env::var("FLUTTER_ROOT") {
14            PathBuf::from(root)
15        } else {
16            let flutter = which::which("flutter").or(Err(Error::FlutterNotFound))?;
17            let flutter = std::fs::canonicalize(flutter)?;
18            flutter
19                .parent()
20                .ok_or(Error::FlutterNotFound)?
21                .parent()
22                .ok_or(Error::FlutterNotFound)?
23                .to_owned()
24        };
25        Ok(Flutter { root })
26    }
27
28    pub fn root(&self) -> &Path {
29        &self.root
30    }
31
32    pub fn flutter(&self) -> Result<PathBuf, Error> {
33        which::which("flutter").or(Err(Error::FlutterNotFound))
34    }
35
36    pub fn engine_version(&self) -> Result<String, Error> {
37        let path = self
38            .root
39            .join("bin")
40            .join("internal")
41            .join("engine.version");
42        Ok(std::fs::read_to_string(path).map(|v| v.trim().to_owned())?)
43    }
44
45    pub fn bundle(&self, cargo: &Cargo, build: Build, dart_main: &Path) -> Result<(), Error> {
46        let flag = match build {
47            Build::Debug => "--debug",
48            Build::Release => "--release",
49            Build::Profile => "--profile",
50        };
51        let status = Command::new(self.flutter()?)
52            .current_dir(cargo.workspace().root())
53            .arg("build")
54            .arg("bundle")
55            .arg(flag)
56            .arg("--track-widget-creation")
57            .arg("--asset-dir")
58            .arg(cargo.build_dir().join("flutter_assets"))
59            .arg("--depfile")
60            .arg(cargo.build_dir().join("snapshot_blob.bin.d"))
61            .arg("--target")
62            .arg(dart_main)
63            .status()
64            .expect("flutter build bundle");
65        if status.code() != Some(0) {
66            return Err(Error::FlutterError);
67        }
68        Ok(())
69    }
70
71    pub fn attach(&self, cargo: &Cargo, debug_uri: &str) -> Result<(), Error> {
72        let status = Command::new(self.flutter()?)
73            .current_dir(cargo.workspace().root())
74            .arg("attach")
75            .arg("--device-id=flutter-tester")
76            .arg(format!("--debug-uri={}", debug_uri))
77            .status()
78            .expect("Success");
79        if status.code() != Some(0) {
80            return Err(Error::FlutterError);
81        }
82        Ok(())
83    }
84
85    pub fn aot(
86        &self,
87        cargo: &Cargo,
88        host_engine: &Engine,
89        target_engine: &Engine,
90    ) -> Result<(), Error> {
91        let root = cargo.workspace().root();
92        let build_dir = cargo.build_dir();
93        let host_engine_dir = host_engine.engine_dir();
94        let target_engine_dir = target_engine.engine_dir();
95        let snapshot = build_dir.join("kernel_snapshot.dill");
96
97        let status = Command::new(host_engine.dart()?)
98            .current_dir(root)
99            .arg(
100                host_engine_dir
101                    .join("gen")
102                    .join("frontend_server.dart.snapshot"),
103            )
104            .arg("--sdk-root")
105            .arg(host_engine_dir.join("flutter_patched_sdk"))
106            .arg("--target=flutter")
107            .arg("--aot")
108            .arg("--tfa")
109            .arg("-Ddart.vm.product=true")
110            .arg("--packages")
111            .arg(".packages")
112            .arg("--output-dill")
113            .arg(&snapshot)
114            .arg(root.join("lib").join("main.dart"))
115            .status()
116            .expect("Success");
117
118        if status.code() != Some(0) {
119            return Err(Error::FlutterError);
120        }
121
122        let gen_snapshot = [
123            "gen_snapshot",
124            "gen_snapshot_x64",
125            "gen_snapshot_x86",
126            "gen_snapshot_host_targeting_host",
127            "gen_snapshot.exe",
128        ]
129        .iter()
130        .map(|bin| target_engine_dir.join(bin))
131        .find(|path| path.exists())
132        .ok_or(Error::GenSnapshotNotFound)?;
133
134        let status = Command::new(gen_snapshot)
135            .current_dir(root)
136            .arg("--causal_async_stacks")
137            .arg("--deterministic")
138            .arg("--snapshot_kind=app-aot-elf")
139            .arg("--strip")
140            .arg(format!("--elf={}", build_dir.join("app.so").display()))
141            .arg(&snapshot)
142            .status()
143            .expect("Success");
144
145        if status.code() != Some(0) {
146            return Err(Error::FlutterError);
147        }
148
149        Ok(())
150    }
151
152    pub fn drive(
153        &self,
154        host_engine: &Engine,
155        cargo: &Cargo,
156        debug_uri: &str,
157        dart_main: &Path,
158    ) -> Result<(), Error> {
159        let mut file = dart_main.file_stem().unwrap().to_owned();
160        file.push("_test.dart");
161        let driver = dart_main.parent().unwrap().join(file);
162
163        // used by flutter_driver
164        std::env::set_var("VM_SERVICE_URL", debug_uri);
165        let status = Command::new(host_engine.dart()?)
166            .current_dir(cargo.workspace().root())
167            .arg(driver)
168            .status()
169            .expect("Success");
170        if status.code() != Some(0) {
171            return Err(Error::FlutterError);
172        }
173        Ok(())
174    }
175}