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        view_process_main()
23    }
24});
25
26/// 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}
38
39/// 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}
57
58/// Dynamically linked pre-built view.
59pub struct ViewLib {
60    view_process_main_fn: unsafe extern "C" fn(&StaticPatch),
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.
66    pub fn install() -> Result<Self, Error> {
67        let dir = env::temp_dir().join("zng_view");
68        std::fs::create_dir_all(&dir)?;
69        Self::install_to(dir)
70    }
71
72    /// Try to delete the installed library from the temp directory.
73    ///
74    /// See [`uninstall_from`] for details.
75    ///
76    /// [`uninstall_from`]: Self::uninstall_from
77    pub fn uninstall() -> Result<bool, io::Error> {
78        let dir = env::temp_dir().join("zng_view");
79        Self::uninstall_from(dir)
80    }
81
82    /// Extract the embedded library to `dir` and link to it.
83    pub fn install_to(dir: impl Into<PathBuf>) -> Result<Self, Error> {
84        Self::install_to_impl(dir.into())
85    }
86    fn install_to_impl(dir: PathBuf) -> Result<Self, Error> {
87        #[cfg(not(zng_lib_embedded))]
88        {
89            let _ = dir;
90            panic!("library not embedded");
91        }
92
93        #[cfg(zng_lib_embedded)]
94        {
95            let file = Self::install_path(dir);
96
97            if !file.exists() {
98                std::fs::write(&file, LIB)?;
99            }
100
101            Self::link(file)
102        }
103    }
104
105    /// 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.
112    pub fn uninstall_from(dir: impl Into<PathBuf>) -> Result<bool, io::Error> {
113        Self::uninstall_from_impl(dir.into())
114    }
115    fn uninstall_from_impl(dir: PathBuf) -> Result<bool, io::Error> {
116        #[cfg(not(zng_lib_embedded))]
117        {
118            let _ = dir;
119            Ok(false)
120        }
121
122        #[cfg(zng_lib_embedded)]
123        {
124            let file = Self::install_path(dir);
125
126            if file.exists() {
127                std::fs::remove_file(file)?;
128                Ok(true)
129            } else {
130                Ok(false)
131            }
132        }
133    }
134
135    #[cfg(zng_lib_embedded)]
136    fn install_path(dir: PathBuf) -> PathBuf {
137        #[cfg(target_os = "windows")]
138        let file_name = format!("{LIB_NAME}.dll");
139        #[cfg(target_os = "linux")]
140        let file_name = format!("{LIB_NAME}.so");
141        #[cfg(target_os = "macos")]
142        let file_name = format!("{LIB_NAME}.dylib");
143
144        dir.join(file_name)
145    }
146
147    /// 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.
154    pub fn link(view_dylib: impl Into<PathBuf>) -> Result<Self, Error> {
155        Self::link_impl(view_dylib.into())
156    }
157    fn link_impl(mut lib: PathBuf) -> Result<Self, Error> {
158        if !lib.exists() && lib.extension().is_none() {
159            #[cfg(target_os = "windows")]
160            lib.set_extension("dll");
161            #[cfg(target_os = "linux")]
162            lib.set_extension("so");
163            #[cfg(target_os = "macos")]
164            lib.set_extension("dylib");
165        }
166
167        if lib.exists() {
168            // this disables Windows DLL search feature.
169            lib = dunce::canonicalize(lib)?;
170        }
171
172        if !lib.exists() {
173            return Err(io::Error::new(io::ErrorKind::NotFound, format!("view library not found in `{}`", lib.display())).into());
174        }
175
176        unsafe {
177            let lib = Library::new(lib)?;
178            Ok(ViewLib {
179                view_process_main_fn: *match lib.get(b"extern_view_process_main") {
180                    Ok(f) => f,
181                    // try old name (<=0.6.2)
182                    Err(e) => match lib.get(b"extern_init") {
183                        Ok(f) => f,
184                        Err(_) => return Err(e.into()),
185                    },
186                },
187                run_same_process_fn: *lib.get(b"extern_run_same_process")?,
188                _lib: lib,
189            })
190        }
191    }
192
193    /// 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
201    pub fn view_process_main(self) {
202        let patch = StaticPatch::capture();
203        unsafe { (self.view_process_main_fn)(&patch) }
204    }
205
206    /// Call the pre-build [`run_same_process`].
207    ///
208    /// This function exits the process after `run_app` returns.
209    ///
210    /// # Aborts
211    ///
212    /// 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,
213    /// this needs to happen because unwind across FFI in undefined behavior.
214    ///
215    /// [`run_same_process`]: https://docs.rs/zng-view/fn.run_same_process.html
216    pub fn run_same_process(self, run_app: impl FnOnce() + Send + 'static) -> ! {
217        self.run_same_process_impl(Box::new(run_app))
218    }
219    fn run_same_process_impl(self, run_app: Box<dyn FnOnce() + Send>) -> ! {
220        let patch = StaticPatch::capture();
221
222        enum Run {
223            Waiting,
224            Set(Box<dyn FnOnce() + Send>),
225            Taken,
226        }
227        static RUN: Mutex<Run> = Mutex::new(Run::Waiting);
228
229        match mem::replace(&mut *RUN.lock(), Run::Set(Box::new(run_app))) {
230            Run::Waiting => {}
231            _ => panic!("expected only one call to `run_same_process`"),
232        };
233
234        extern "C" fn run() {
235            match mem::replace(&mut *RUN.lock(), Run::Taken) {
236                Run::Set(run_app) => run_app(),
237                _ => unreachable!(),
238            }
239        }
240
241        // SAFETY: we need to trust a compatible library is loaded at this point
242        unsafe {
243            (self.run_same_process_fn)(&patch, run);
244        }
245
246        // exit the process to ensure all threads are stopped
247        zng_env::exit(0)
248    }
249}
250
251#[cfg(zng_lib_embedded)]
252const LIB: &[u8] = include_bytes!(env!("ZNG_VIEW_LIB"));
253#[cfg(zng_lib_embedded)]
254const LIB_NAME: &str = concat!("zv.", env!("CARGO_PKG_VERSION"), ".", env!("ZNG_VIEW_LIB_HASH"));
255
256/// Error searching or linking to pre-build library.
257#[derive(Debug)]
258#[non_exhaustive]
259pub enum Error {
260    /// Error searching library.
261    Io(io::Error),
262    /// Error loading or linking library.
263    Lib(libloading::Error),
264}
265impl From<io::Error> for Error {
266    fn from(e: io::Error) -> Self {
267        Error::Io(e)
268    }
269}
270impl From<libloading::Error> for Error {
271    fn from(e: libloading::Error) -> Self {
272        Error::Lib(e)
273    }
274}
275impl fmt::Display for Error {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        match self {
278            Error::Io(e) => write!(f, "{e}"),
279            Error::Lib(e) => write!(f, "{e}"),
280        }
281    }
282}
283impl std::error::Error for Error {
284    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
285        match self {
286            Error::Io(e) => Some(e),
287            Error::Lib(e) => Some(e),
288        }
289    }
290}