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 '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 ` '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//! # use zng::wizard::WIZARD;
137//! # fn demo(sfx: SfxClient) -> UiNode {
138//! let destination_page = page::InstallDirPage::new();
139//! let destination = destination_page.install_dir.clone();
140//! SetupWizard! {
141//! // Set window title from page
142//! get_title = WINDOW.vars().title();
143//!
144//! pages = vec![page::WelcomePage::new("").build(), destination_page.build()];
145//! setup_op = SetupOp::Install;
146//!
147//! on_begin = async_hn!(destination, sfx, |args| {
148//! args.propagation.stop();
149//!
150//! // define install operation
151//! let mut install_cfg = InstallConfig::new();
152//!
153//! // first task, extract TAR read from SFX
154//! let data = sfx.read("data").await.unwrap().into_blocking().await;
155//! let extract_cfg = setup_task::ExtractTarConfig::from_sfx(data, destination.get());
156//! install_cfg.push::<setup_task::ExtractTar>("extract", extract_cfg);
157//!
158//! // begin install
159//! let r = SETUP.install(install_cfg, None);
160//!
161//! // go to progress page, wizard cannot back to `pages` after this call
162//! WIZARD.selected_page().set(page::StatusPage::new().build());
163//!
164//! // wait
165//! let uninstall_cfg = r.wait_rsp().await.unwrap();
166//!
167//! // go to finish page
168//! WIZARD.selected_page().set(page::FinishPage::new().build());
169//! });
170//! }
171//! # }
172//! ```
173//!
174//! [`SETUP.install`]: SETUP::install
175//! [`SetupWizard!`]: struct@SetupWizard
176//! [`clap::Parser::parse_from`]: https://docs.rs/clap/latest/clap/trait.Parser.html#method.parse_from
177//!
178//! # Full API
179//!
180//! See [`zng_ext_setup`] and [`zng_wgt_setup`] for the full API.
181
182pub use zng_wgt_setup::{APP_ID_VAR, APP_NAME_VAR, APP_ORG_VAR, APP_VERSION_VAR, SETUP_OP_VAR, SetupOp, SetupWizard};
183
184pub use zng_ext_setup::{
185 InstallConfig, PreparedInstallConfig, SETUP, SetupError, SetupErrorState, SetupOpStatus, SetupStatus, SfxClient, SfxDataInfo, SfxError,
186 UninstallConfig,
187};
188
189/// Common setup tasks and custom task API.
190///
191/// Setup tasks can be instantiated with [`InstallConfig::push`], usually after configuration
192/// is collected using a [`SetupWizard!`] or CLI.
193///
194/// [`SetupWizard!`]: struct@SetupWizard
195///
196/// # Full API
197///
198/// See [`zng_ext_setup::task`] for the full API.
199pub mod task {
200 pub use zng_ext_setup::task::{
201 CopyCurrentExe, CopyCurrentExeConfig, ExtractTar, ExtractTarConfig, SetupTask, SetupTaskError, TaskTypeId,
202 };
203
204 #[cfg(windows)]
205 pub use zng_ext_setup::task::{RegisterUninstaller, RegisterUninstallerConfig};
206
207 #[cfg(any(windows, target_os = "linux"))]
208 pub use zng_ext_setup::task::{CreateShortcut, CreateShortcutConfig};
209}
210
211/// Common setup wizard pages.
212///
213/// The types in this module are builders for [`zng::wizard::Page`] instances that
214/// can be used with [`SetupWizard!`].
215///
216/// [`SetupWizard!`]: struct@SetupWizard
217///
218/// # Full API
219///
220/// See [`zng_wgt_setup::page`] for the full API.
221pub mod page {
222 pub use zng_wgt_setup::page::{
223 EulaPage, EulaTxt, FailedPage, FailedTaskInfoFnArgs, FinishAction, FinishPage, InstallDirPage, StatusPage, StatusTaskInfoFnArgs,
224 TaskInfo, WelcomePage,
225 };
226}