zng/setup.rs
1#![cfg(feature = "setup")]
2
3//! Widgets for setup UI and a service for implementing it.
4//!
5//! The [`SETUP`] service manages running setup operations. Operations are
6//! composed of [`task::SetupTask`] that are the building blocks of installing, updating
7//! and uninstalling an app or resource on the operating system. To start, a list of
8//! tasks is grouped into an [`InstallConfig`] that runs in [`SETUP.install`].
9//!
10//! The [`SetupWizard!`] widget can be used together with [`page`] builders to create the
11//! classic Windows setup experience. Usually the wizard starts with a set of config pages,
12//! once these pages are finished an [`InstallConfig`] is built and started, the set of pages
13//! is replaced with a progress page, once the operation is done the pages are replaced again
14//! with a a results page.
15//!
16//! Note that the wizard widget is not required, a completely custom UI could also build an [`InstallConfig`],
17//! the [`SETUP`] service has no connection with what UI is using it, it can even be operated from a command
18//! line interface (CLI) in a headless app.
19//!
20//! Note that this module is only tested on Windows, it should work in other operating systems, some
21//! tasks make a minimal effort to be cross platform, but Windows is the only OS without a standard way
22//! of installing apps, so it is the focus of this API.
23//!
24//! # SFX Setup
25//!
26//! The `cargo zng res --tool sfx` tool can be used to build a self-extracting executable
27//! that extracts and runs a setup program implemented using this module. The examples that follow
28//! show how to implement a setup app in the same executable as the app that will be installed.
29//!
30//! The reasons for implementing on the same executable are:
31//!
32//! * Reduced payload size. All the heavy dependencies like the renderer are shared with the setup app.
33//! * Consistent visual identity. Any themes applied to the app are naturally used in the setup app.
34//! * Shared config. Settings like language preference selected during setup naturally apply to the installed app.
35//! * Shared metadata. The [`zng::env::about`] metadata is used by default in [`SetupWizard!`] pages.
36//!
37//! ## Pack
38//!
39//! Before implementing the setup app lets overview how the `cargo zng res` command will be used to package
40//! it and the rest of the app data in a single setup executable.
41//!
42//! The `pack/windows/my-app-setup.exe.zr-sfxf` file:
43//!
44//! ```toml
45//! [sfx]
46//! run = "target/release/my-app.exe"
47//!
48//! [[data]]
49//! name = "data"
50//! compress = "zstd"
51//! file = "target/pack/windows/setup/data.tar"
52//! ```
53//!
54//! The command `cargo zng res --pack "pack/windows" "target/pack/windows"` will generate a `target/pack/windows/my-app-setup.exe`
55//! file that contains the executable and data, both compressed. Note that the data is pre-packed into a `tar`, this can be
56//! done using the `.zr-tar` tool, outside of the scope of this example.
57//!
58//! When the `my-app-setup.exe` runs it extracts the `my-app.exe` and runs it with the `SFX_ARGS` environment variable set.
59//! Inside the `my-app.exe` this variable causes the setup app to run, instead of the normal app.
60//!
61//! The `my-app.exe` will run from a temp dir, without any accompanying files. It must use the `SFX_ARGS` to call
62//! the `my-app-setup.exe` in server mode to read any extra file it requires to run, and also to extract the install
63//! payload.
64//!
65//! Call `cargo zng res --tool sfx` to read detailed docs about this tool.
66//!
67//! ## Setup App
68//!
69//! In this example the setup app is implemented on the same crate as the installed app.
70//!
71//! ```
72//! # mod demo_no_run {
73//! use zng::prelude::*;
74//!
75//! fn main() {
76//! zng::env::init!();
77//!
78//! // if "SFX_ARGS" is set intercept and run setup app
79//! #[cfg(windows)]
80//! windows_setup::setup_main();
81//! // if not, run normal app
82//! app_main();
83//! }
84//!
85//! #[cfg(windows)]
86//! mod windows_setup {
87//! use zng::prelude::*;
88//! use zng::setup::{task as setup_task, *};
89//!
90//! pub fn setup_main() {
91//! // connect if a valid SFX_ARGS is set.
92//! let sfx = match SfxClient::connect_blocking() {
93//! Ok(c) => c,
94//! Err(e) => return assert!(e.is_no_sfx(), "{e}"),
95//! };
96//!
97//! // basic CLI parsing
98//! let destination = match sfx.sfx_args().get(1) {
99//! Some(d) => d.clone(),
100//! // print CLI help, SFX replicates all stdout/err
101//! None => return println!("{} DESTINATION", sfx.sfx_args()[0]),
102//! };
103//!
104//! let mut app = APP.defaults().run_headless(false);
105//! app.run_task(async move {
106//! // define install operation
107//! let mut install_cfg = InstallConfig::new();
108//!
109//! // first task, extract TAR read from SFX
110//! let data = sfx.read("data").await.unwrap().into_blocking().await;
111//! let extract_cfg = setup_task::ExtractTarConfig::from_sfx(data, destination.into());
112//! install_cfg.push::<setup_task::ExtractTar>("extract", extract_cfg);
113//!
114//! // install
115//! let uninstall_cfg = SETUP.install(install_cfg, None).wait_rsp().await.unwrap();
116//! });
117//!
118//! // interrupt normal app
119//! zng::env::exit(0);
120//! }
121//! }
122//! # fn app_main() { }
123//! # }
124//! ```
125//!
126//! In the example above a headless app is used to run a very simple install operation configured directly
127//! from CLI. In a full setup app both CLI and UI modes should be provided, with a CLI parsed by something
128//! more robust like [`clap::Parser::parse_from`].
129//!
130//! The following example shows a [`SetupWizard!`] that does the same simple install operation, configured
131//! by the user using a GUI.
132//!
133//! ```
134//! # use zng::prelude::*;
135//! # use zng::setup::{task as setup_task, *};
136//! # fn demo(sfx: SfxClient) -> UiNode {
137//! let destination_page = page::InstallDirPage::new();
138//! let destination = destination_page.install_dir.clone();
139//! SetupWizard! {
140//! // Set window title from page
141//! get_title = WINDOW.vars().title();
142//!
143//! pages = vec![page::WelcomePage::new("").build(), destination_page.build()];
144//! setup_op = SetupOp::Install;
145//!
146//! on_finish = async_hn!(destination, sfx, |args| {
147//! args.propagation.stop();
148//!
149//! let mut cfg = InstallConfig::new();
150//!
151//! let data = sfx.read("data").await.unwrap().into_blocking().await;
152//! cfg.push::<setup_task::ExtractTar>("extract", setup_task::ExtractTarConfig::from_sfx(data, destination.get()));
153//!
154//! let r = SETUP.install(cfg, None);
155//!
156//! let uninstall = r.wait_rsp().await.unwrap();
157//! });
158//! }
159//! # }
160//! ```
161//!
162//! [`SETUP.install`]: SETUP::install
163//! [`SetupWizard!`]: struct@SetupWizard
164//! [`clap::Parser::parse_from`]: https://docs.rs/clap/latest/clap/trait.Parser.html#method.parse_from
165//!
166//! # Full API
167//!
168//! See [`zng_ext_setup`] and [`zng_wgt_setup`] for the full API.
169
170pub use zng_wgt_setup::{APP_ID_VAR, APP_NAME_VAR, APP_ORG_VAR, APP_VERSION_VAR, SETUP_OP_VAR, SetupOp, SetupWizard};
171
172pub use zng_ext_setup::{
173 InstallConfig, PreparedInstallConfig, SETUP, SetupError, SetupErrorState, SetupOpStatus, SfxClient, SfxDataInfo, SfxError,
174 UninstallConfig,
175};
176
177/// Common setup tasks and custom task API.
178///
179/// Setup tasks can be instantiated with [`InstallConfig::push`], usually after configuration
180/// is collected using a [`SetupWizard!`] or CLI.
181///
182/// [`SetupWizard!`]: struct@SetupWizard
183///
184/// # Full API
185///
186/// See [`zng_ext_setup::task`] for the full API.
187pub mod task {
188 pub use zng_ext_setup::task::{CopyCurrentExe, CopyCurrentExeConfig, ExtractTar, ExtractTarConfig, SetupTask, SetupTaskError};
189
190 #[cfg(windows)]
191 pub use zng_ext_setup::task::{RegisterUninstaller, RegisterUninstallerConfig};
192
193 #[cfg(any(windows, target_os = "linux"))]
194 pub use zng_ext_setup::task::{CreateShortcut, CreateShortcutConfig};
195}
196
197/// Common setup wizard pages.
198///
199/// The types in this module are builders for [`zng::wizard::Page`] instances that
200/// can be used with [`SetupWizard!`].
201///
202/// [`SetupWizard!`]: struct@SetupWizard
203///
204/// # Full API
205///
206/// See [`zng_wgt_setup::page`] for the full API.
207pub mod page {
208 pub use zng_wgt_setup::page::{EulaPage, EulaTxt, InstallDirPage, WelcomePage};
209}