zng_view_prebuilt/
lib.rs

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