1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Dynamically links to [`zng-view`] pre-built library.
5//!
6//! [`zng-view`]: https://docs.rs/zng-view
7//!
8//! # Crate
9//!
10#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
11#![warn(unused_extern_crates)]
12#![warn(missing_docs)]
1314use core::fmt;
15use libloading::*;
16use parking_lot::Mutex;
17use std::{env, io, mem, path::PathBuf};
18use zng_view_api::StaticPatch;
1920zng_env::on_process_start!(|_| {
21if std::env::var("ZNG_VIEW_NO_INIT_START").is_err() {
22 view_process_main()
23 }
24});
2526/// Calls the prebuilt [`view_process_main`].
27///
28/// Note that this only needs to be called if the view-process is not built on the same executable, if
29/// it is you only need to call [`zng_env::init!`] at the beginning of the executable main.
30///
31/// You can also disable start on init by setting the `ZNG_VIEW_NO_INIT_START` environment variable. In this
32/// case you must manually call this function.
33///
34/// [`view_process_main`]: https://docs.rs/zng-view/fn.view_process_main.html
35pub fn view_process_main() {
36 ViewLib::install().unwrap().view_process_main()
37}
3839/// Call the prebuilt [`run_same_process`].
40///
41/// This function exits the process after `run_app` returns.
42///
43/// [`run_same_process`]: https://docs.rs/zng-view/fn.run_same_process.html
44///
45/// # Panics
46///
47/// Panics if it fails to [install] the prebuilt binary.
48///
49/// # Aborts
50///
51/// Kills the process with code `101` if there is a panic generated by the pre-built code or by threads started by the pre-build code.
52///
53/// [install]: ViewLib::install
54pub fn run_same_process(run_app: impl FnOnce() + Send + 'static) -> ! {
55 ViewLib::install().unwrap().run_same_process(run_app)
56}
5758/// Dynamically linked pre-built view.
59pub struct ViewLib {
60 view_process_main_fn: unsafe extern "C" fn(),
61 run_same_process_fn: unsafe extern "C" fn(&StaticPatch, extern "C" fn()),
62 _lib: Library,
63}
64impl ViewLib {
65/// Extract the embedded library to the temp directory and link to it.
66pub fn install() -> Result<Self, Error> {
67let dir = env::temp_dir().join("zng_view");
68 std::fs::create_dir_all(&dir)?;
69Self::install_to(dir)
70 }
7172/// Try to delete the installed library from the temp directory.
73 ///
74 /// See [`uninstall_from`] for details.
75 ///
76 /// [`uninstall_from`]: Self::uninstall_from
77pub fn uninstall() -> Result<bool, io::Error> {
78let dir = env::temp_dir().join("zng_view");
79Self::uninstall_from(dir)
80 }
8182/// Extract the embedded library to `dir` and link to it.
83pub fn install_to(dir: impl Into<PathBuf>) -> Result<Self, Error> {
84Self::install_to_impl(dir.into())
85 }
86fn install_to_impl(dir: PathBuf) -> Result<Self, Error> {
87#[cfg(not(zng_lib_embedded))]
88{
89let _ = dir;
90panic!("library not embedded");
91 }
9293#[cfg(zng_lib_embedded)]
94{
95let file = Self::install_path(dir);
9697if !file.exists() {
98 std::fs::write(&file, LIB)?;
99 }
100101Self::link(file)
102 }
103 }
104105/// Try to delete the installed library from the given `dir`.
106 ///
107 /// Returns `Ok(true)` if uninstalled, `Ok(false)` if was not installed and `Err(_)`
108 /// if is installed and failed to delete.
109 ///
110 /// Note that the file is probably in use if it was installed in the current process instance, in Windows
111 /// files cannot be deleted until they are released.
112pub fn uninstall_from(dir: impl Into<PathBuf>) -> Result<bool, io::Error> {
113Self::uninstall_from_impl(dir.into())
114 }
115fn uninstall_from_impl(dir: PathBuf) -> Result<bool, io::Error> {
116#[cfg(not(zng_lib_embedded))]
117{
118let _ = dir;
119Ok(false)
120 }
121122#[cfg(zng_lib_embedded)]
123{
124let file = Self::install_path(dir);
125126if file.exists() {
127 std::fs::remove_file(file)?;
128Ok(true)
129 } else {
130Ok(false)
131 }
132 }
133 }
134135#[cfg(zng_lib_embedded)]
136fn install_path(dir: PathBuf) -> PathBuf {
137#[cfg(target_os = "windows")]
138let file_name = format!("{LIB_NAME}.dll");
139#[cfg(target_os = "linux")]
140let file_name = format!("{LIB_NAME}.so");
141#[cfg(target_os = "macos")]
142let file_name = format!("{LIB_NAME}.dylib");
143144 dir.join(file_name)
145 }
146147/// Link to the pre-built library file.
148 ///
149 /// If the file does not have an extension searches for a file without extension then a
150 /// `.dll` file in Windows, a `.so` file in Linux and a `.dylib` file in other operating systems.
151 ///
152 /// Note that the is only searched as described above, if it is not found an error returns immediately,
153 /// the operating system library search feature is not used.
154pub fn link(view_dylib: impl Into<PathBuf>) -> Result<Self, Error> {
155Self::link_impl(view_dylib.into())
156 }
157fn link_impl(mut lib: PathBuf) -> Result<Self, Error> {
158if !lib.exists() && lib.extension().is_none() {
159#[cfg(target_os = "windows")]
160lib.set_extension("dll");
161#[cfg(target_os = "linux")]
162lib.set_extension("so");
163#[cfg(target_os = "macos")]
164lib.set_extension("dylib");
165 }
166167if lib.exists() {
168// this disables Windows DLL search feature.
169lib = dunce::canonicalize(lib)?;
170 }
171172if !lib.exists() {
173return Err(io::Error::new(io::ErrorKind::NotFound, format!("view library not found in `{}`", lib.display())).into());
174 }
175176unsafe {
177let lib = Library::new(lib)?;
178Ok(ViewLib {
179 view_process_main_fn: *match lib.get(b"extern_view_process_main") {
180Ok(f) => f,
181// try old name (<=0.6.2)
182Err(e) => match lib.get(b"extern_init") {
183Ok(f) => f,
184Err(_) => return Err(e.into()),
185 },
186 },
187 run_same_process_fn: *lib.get(b"extern_run_same_process")?,
188 _lib: lib,
189 })
190 }
191 }
192193/// Call the pre-built [`view_process_main`].
194 ///
195 /// # Aborts
196 ///
197 /// Kills the process with code `101` if there is a panic generated by the pre-built code or by threads started by the pre-build code,
198 /// this needs to happen because unwind across FFI in undefined behavior.
199 ///
200 /// [`view_process_main`]: https://docs.rs/zng-view/fn.view_process_main.html
201pub fn view_process_main(self) {
202unsafe { (self.view_process_main_fn)() }
203 }
204205/// Call the pre-build [`run_same_process`].
206 ///
207 /// This function exits the process after `run_app` returns.
208 ///
209 /// # Aborts
210 ///
211 /// Kills the process with code `101` if there is a panic generated by the pre-built code or by threads started by the pre-build code,
212 /// this needs to happen because unwind across FFI in undefined behavior.
213 ///
214 /// [`run_same_process`]: https://docs.rs/zng-view/fn.run_same_process.html
215pub fn run_same_process(self, run_app: impl FnOnce() + Send + 'static) -> ! {
216self.run_same_process_impl(Box::new(run_app))
217 }
218fn run_same_process_impl(self, run_app: Box<dyn FnOnce() + Send>) -> ! {
219let patch = StaticPatch::capture();
220221enum Run {
222 Waiting,
223 Set(Box<dyn FnOnce() + Send>),
224 Taken,
225 }
226static RUN: Mutex<Run> = Mutex::new(Run::Waiting);
227228match mem::replace(&mut *RUN.lock(), Run::Set(Box::new(run_app))) {
229 Run::Waiting => {}
230_ => panic!("expected only one call to `run_same_process`"),
231 };
232233extern "C" fn run() {
234match mem::replace(&mut *RUN.lock(), Run::Taken) {
235 Run::Set(run_app) => run_app(),
236_ => unreachable!(),
237 }
238 }
239240// SAFETY: we need to trust a compatible library is loaded at this point
241unsafe {
242 (self.run_same_process_fn)(&patch, run);
243 }
244245// exit the process to ensure all threads are stopped
246zng_env::exit(0)
247 }
248}
249250#[cfg(zng_lib_embedded)]
251const LIB: &[u8] = include_bytes!(env!("ZNG_VIEW_LIB"));
252#[cfg(zng_lib_embedded)]
253const LIB_NAME: &str = concat!("zv.", env!("CARGO_PKG_VERSION"), ".", env!("ZNG_VIEW_LIB_HASH"));
254255/// Error searching or linking to pre-build library.
256#[derive(Debug)]
257pub enum Error {
258/// Error searching library.
259Io(io::Error),
260/// Error loading or linking library.
261Lib(libloading::Error),
262}
263impl From<io::Error> for Error {
264fn from(e: io::Error) -> Self {
265 Error::Io(e)
266 }
267}
268impl From<libloading::Error> for Error {
269fn from(e: libloading::Error) -> Self {
270 Error::Lib(e)
271 }
272}
273impl fmt::Display for Error {
274fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275match self {
276 Error::Io(e) => write!(f, "{e}"),
277 Error::Lib(e) => write!(f, "{e}"),
278 }
279 }
280}
281impl std::error::Error for Error {
282fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
283match self {
284 Error::Io(e) => Some(e),
285 Error::Lib(e) => Some(e),
286 }
287 }
288}