zng_view_prebuilt/
lib.rs

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)]
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(),
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        unsafe { (self.view_process_main_fn)() }
203    }
204
205    /// 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
215    pub fn run_same_process(self, run_app: impl FnOnce() + Send + 'static) -> ! {
216        self.run_same_process_impl(Box::new(run_app))
217    }
218    fn run_same_process_impl(self, run_app: Box<dyn FnOnce() + Send>) -> ! {
219        let patch = StaticPatch::capture();
220
221        enum Run {
222            Waiting,
223            Set(Box<dyn FnOnce() + Send>),
224            Taken,
225        }
226        static RUN: Mutex<Run> = Mutex::new(Run::Waiting);
227
228        match 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        };
232
233        extern "C" fn run() {
234            match mem::replace(&mut *RUN.lock(), Run::Taken) {
235                Run::Set(run_app) => run_app(),
236                _ => unreachable!(),
237            }
238        }
239
240        // SAFETY: we need to trust a compatible library is loaded at this point
241        unsafe {
242            (self.run_same_process_fn)(&patch, run);
243        }
244
245        // exit the process to ensure all threads are stopped
246        zng_env::exit(0)
247    }
248}
249
250#[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"));
254
255/// Error searching or linking to pre-build library.
256#[derive(Debug)]
257pub enum Error {
258    /// Error searching library.
259    Io(io::Error),
260    /// Error loading or linking library.
261    Lib(libloading::Error),
262}
263impl From<io::Error> for Error {
264    fn from(e: io::Error) -> Self {
265        Error::Io(e)
266    }
267}
268impl From<libloading::Error> for Error {
269    fn from(e: libloading::Error) -> Self {
270        Error::Lib(e)
271    }
272}
273impl fmt::Display for Error {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        match self {
276            Error::Io(e) => write!(f, "{e}"),
277            Error::Lib(e) => write!(f, "{e}"),
278        }
279    }
280}
281impl std::error::Error for Error {
282    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
283        match self {
284            Error::Io(e) => Some(e),
285            Error::Lib(e) => Some(e),
286        }
287    }
288}