Skip to main content

zoi/
lib.rs

1//! # Zoi: The Advanced Package Manager & Environment Orchestrator
2//!
3//! This crate provides the core functionality of Zoi as a library, allowing
4//! other Rust applications to leverage its package management and environment
5//! setup capabilities.
6//!
7//! Architectural Design:
8//! Zoi's library API is designed around "Pragmatic Transactionality". It allows
9//! programmatic control over the two-phase installation process, SAT-based
10//! dependency resolution, and cryptographically verified registry state.
11//!
12//! For user documentation please visit [Zoi's Docs](https://zillowe.qzz.io/docs/zds/zoi).
13//!
14//! ## Key Library Entry Points:
15//! - `install_sources`: The high-level API used by the CLI for standard
16//!   installations.
17//! - `resolve_dependency_graph`: Calculate required packages without modifying
18//!   disk.
19//! - `build_with_options`: Create distributable `.zpa` archives.
20//!
21//! ## Getting Started
22//!
23//! To use Zoi as a library, add it using `cargo` or as a dependency in your
24//! `Cargo.toml`:
25//!
26//! ```sh
27//! cargo add zoi-rs
28//! ```
29//!
30//! ```toml
31//! [dependencies]
32//! zoi-rs = "1"
33//! ```
34//!
35//! ## Example: Install a package
36//!
37//! ```no_run
38//! use std::path::Path;
39//!
40//! use anyhow::Result;
41//! use zoi::{Scope, install_package_with_options};
42//!
43//! fn main() -> Result<()> {
44//!     let archive_path =
45//!         Path::new("path/to/your/package-1.0.0-linux-amd64.zpa");
46//!     let options = zoi::PackageInstallOptions {
47//!         scope_override: Some(Scope::User),
48//!         registry_handle: "local".to_string(),
49//!         yes: true,
50//!         ..Default::default()
51//!     };
52//!
53//!     let installed_files =
54//!         install_package_with_options(archive_path, &options)?;
55//!
56//!     println!(
57//!         "Package installed successfully. {} files were installed.",
58//!         installed_files.len()
59//!     );
60//!
61//!     Ok(())
62//! }
63//! ```
64
65use std::path::{Path, PathBuf};
66
67use anyhow::Result;
68use colored::Colorize;
69pub use zoi_cli::{cli, cmd, pkg, project};
70pub use zoi_core::types::{self, Scope};
71pub use zoi_core::utils;
72
73/// Options for building a package from a `.pkg.lua` definition.
74#[derive(Debug, Clone)]
75pub struct BuildOptions<'a> {
76    /// Build type to use, such as `source` or `pre-compiled`.
77    pub build_type: Option<&'a str>,
78    /// Target platforms to build for. Use platform strings such as
79    /// `linux-amd64`.
80    pub platforms: Vec<String>,
81    /// Optional PGP key name or fingerprint used to sign the output archive.
82    pub sign_key: Option<String>,
83    /// Optional directory to output the built package to.
84    pub output_dir: Option<PathBuf>,
85    /// Optional sub-packages to build.
86    pub sub_packages: Option<Vec<String>>,
87    /// Whether to install build-time dependencies before building.
88    pub install_deps: bool,
89    /// Whether to run tests before building.
90    pub test: bool,
91    /// Build backend to use. Supported values are `native` and `docker`.
92    pub method: &'a str,
93    /// Docker image to use when `method` is `docker`.
94    pub image: Option<&'a str>,
95    /// Optional package version override.
96    pub version_override: Option<&'a str>,
97    /// Whether to force root ownership (UID/GID 0) in the built archive.
98    pub fakeroot: bool
99}
100
101impl Default for BuildOptions<'_> {
102    fn default() -> Self {
103        Self {
104            build_type: None,
105            platforms: vec![
106                zoi_core::utils::get_platform()
107                    .unwrap_or_else(|_| "linux-amd64".to_string()),
108            ],
109            sign_key: None,
110            output_dir: None,
111            sub_packages: None,
112            install_deps: true,
113            test: false,
114            method: "native",
115            image: None,
116            version_override: None,
117            fakeroot: false
118        }
119    }
120}
121
122/// Options for installing a local `.zpa` archive.
123#[derive(Debug, Clone)]
124pub struct PackageInstallOptions {
125    /// Optional installation scope override.
126    pub scope_override: Option<Scope>,
127    /// Registry handle to record for the installed package. Use `local` for
128    /// local archives.
129    pub registry_handle: String,
130    /// Automatically answer yes to prompts.
131    pub yes: bool,
132    /// Optional split-package names to install from the archive.
133    pub sub_packages: Option<Vec<String>>,
134    /// Whether to create binary links for installed package binaries.
135    pub link_bins: bool
136}
137
138impl Default for PackageInstallOptions {
139    fn default() -> Self {
140        Self {
141            scope_override: Some(Scope::User),
142            registry_handle: "local".to_string(),
143            yes: true,
144            sub_packages: None,
145            link_bins: true
146        }
147    }
148}
149
150/// Options for installing one or more package source strings.
151#[derive(Debug, Clone, Default)]
152pub struct SourceInstallOptions {
153    /// Optional git repository spec for `zoi install --repo`.
154    pub repo: Option<String>,
155    /// Force reinstalling packages that are already installed.
156    pub force: bool,
157    /// Accept all optional dependencies.
158    pub all_optional: bool,
159    /// Automatically answer yes to prompts.
160    pub yes: bool,
161    /// Optional installation scope override.
162    pub scope_override: Option<Scope>,
163    /// Save requested packages to the current project's `zoi.yaml`.
164    pub save: bool,
165    /// Build type to use when building from source.
166    pub build_type: Option<String>,
167    /// Print the install plan without performing the installation.
168    pub dry_run: bool,
169    /// Force building from source even when a prebuilt archive is available.
170    pub build: bool,
171    /// Enforce the current `zoi.lock` exactly for project installs.
172    pub frozen: bool
173}
174
175/// Options for resolving a dependency graph without installing packages.
176#[derive(Debug, Clone, Default)]
177pub struct DependencyResolutionOptions {
178    /// Optional scope to use when resolving dependencies.
179    pub scope_override: Option<Scope>,
180    /// Include packages even when they appear to be installed already.
181    pub force: bool,
182    /// Automatically answer yes to resolver prompts.
183    pub yes: bool,
184    /// Accept all optional dependencies.
185    pub all_optional: bool,
186    /// Build type used for selecting typed build dependencies.
187    pub build_type: Option<String>,
188    /// Suppress non-essential resolver output.
189    pub quiet: bool
190}
191
192/// Result of resolving a single package source.
193#[derive(Debug, Clone)]
194pub struct ResolvedPackage {
195    /// Parsed package metadata.
196    pub package: types::Package,
197    /// Resolved package version.
198    pub version: String,
199    /// Portable manifest information suitable for lockfiles.
200    pub sharable_manifest: Option<types::SharableInstallManifest>,
201    /// Local path to the resolved package definition.
202    pub source_path: PathBuf,
203    /// Registry handle, when the source came from a registry.
204    pub registry_handle: Option<String>,
205    /// Registry repository type (official, community, etc.).
206    pub repo_type: Option<String>,
207    /// Git commit SHA, when the source came from a git repository.
208    pub git_sha: Option<String>
209}
210
211/// Dependency graph resolution result.
212#[derive(Debug)]
213pub struct DependencyResolution {
214    /// Resolved Zoi package graph.
215    pub graph: zoi_install::resolver::DependencyGraph,
216    /// Dependencies handled by external package managers.
217    pub non_zoi_dependencies: Vec<String>
218}
219
220/// Converts a generic Zoi scope to a CLI-specific install scope.
221fn to_install_scope(scope: Scope) -> zoi_cli::cli::InstallScope {
222    match scope {
223        Scope::User => zoi_cli::cli::InstallScope::User,
224        Scope::System => zoi_cli::cli::InstallScope::System,
225        Scope::Project => zoi_cli::cli::InstallScope::Project
226    }
227}
228
229/// Builds a Zoi package from a `.pkg.lua` definition using the provided
230/// options.
231///
232/// # Errors
233///
234/// Returns an error if the build fails.
235pub fn build_with_options(
236    package_file: &Path,
237    options: &BuildOptions<'_>
238) -> Result<()> {
239    let _lock = zoi_core::lock::acquire_lock()?;
240    if options.install_deps {
241        for platform in &options.platforms {
242            let current_platform = if platform == "current" {
243                zoi_core::utils::get_platform()?
244            } else {
245                platform.clone()
246            };
247
248            if let Some(dep_strings) =
249                zoi_package::build::get_build_dependencies(
250                    package_file,
251                    options.build_type,
252                    &current_platform,
253                    options.version_override,
254                    false
255                )?
256                && !dep_strings.is_empty()
257            {
258                println!(
259                    "{} Installing build dependencies...",
260                    "::".bold().blue()
261                );
262                let processed =
263                    std::sync::Mutex::new(std::collections::HashSet::new());
264                let mut installed = Vec::new();
265                for dep_str in dep_strings {
266                    let dep = zoi_deps::parse_dependency_string(&dep_str)?;
267                    zoi_install::dep_install::install_dependency(
268                        &dep,
269                        "build",
270                        zoi_core::types::Scope::User,
271                        true,
272                        true,
273                        &processed,
274                        &mut installed,
275                        None
276                    )?;
277                }
278            }
279        }
280    }
281
282    zoi_package::build::run(
283        package_file,
284        options.build_type,
285        &options.platforms,
286        options.sign_key.clone(),
287        options.output_dir.as_deref(),
288        options.version_override,
289        options.sub_packages.clone(),
290        false,
291        options.method,
292        options.image,
293        options.fakeroot,
294        options.install_deps,
295        options.test
296    )
297}
298
299/// Installs a local `.zpa` package archive using the provided options.
300///
301/// # Errors
302///
303/// Returns an error if the installation fails.
304pub fn install_package_with_options(
305    package_file: &Path,
306    options: &PackageInstallOptions
307) -> Result<Vec<String>> {
308    let _lock = zoi_core::lock::acquire_lock()?;
309    zoi_install::pkg_install::run(
310        package_file,
311        options.scope_override,
312        &options.registry_handle,
313        None,
314        options.yes,
315        options.sub_packages.clone(),
316        options.link_bins,
317        None
318    )
319}
320
321/// Installs one or more package sources using the provided options.
322///
323/// Sources can be registry package names, local `.pkg.lua` files, URLs, or
324/// local manifests.
325///
326/// # Errors
327///
328/// Returns an error if the installation fails.
329pub fn install_sources(
330    sources: &[String],
331    options: &SourceInstallOptions
332) -> Result<()> {
333    let _lock = zoi_core::lock::acquire_lock()?;
334    let plugin_manager = if zoi_core::utils::is_mini_mode() {
335        None
336    } else {
337        let pm = zoi_plugins::PluginManager::new()?;
338        let _ = pm.load_all(options.yes);
339        Some(pm)
340    };
341
342    let pm_ptr = plugin_manager.as_ref();
343
344    zoi_cli::cmd::install::run(
345        sources,
346        options.repo.clone(),
347        options.force,
348        options.all_optional,
349        options.yes,
350        options.scope_override.map(to_install_scope),
351        false,
352        false,
353        options.save,
354        options.build_type.as_deref(),
355        options.dry_run,
356        pm_ptr,
357        options.build,
358        options.frozen,
359        false,
360        false,
361        3,
362        false,
363        false,
364        None
365    )
366}
367
368/// Updates one or more installed packages.
369///
370/// This function checks for updates in the configured registries and performs
371/// a transactional upgrade if a newer version is available.
372///
373/// # Errors
374///
375/// Returns an error if the update fails.
376pub fn update_packages(
377    all: bool,
378    package_names: &[String],
379    yes: bool
380) -> Result<()> {
381    let _lock = zoi_core::lock::acquire_lock()?;
382    zoi_cli::cmd::update::run(
383        all,
384        package_names,
385        yes,
386        false,
387        false,
388        false,
389        false,
390        false
391    )
392}
393
394/// Resolves a single source string into a package and its origin metadata.
395///
396/// # Errors
397///
398/// Returns an error if resolution fails.
399pub fn resolve_package(source: &str, yes: bool) -> Result<ResolvedPackage> {
400    let (
401        package,
402        version,
403        sharable_manifest,
404        source_path,
405        registry_handle,
406        repo_type,
407        git_sha
408    ) = zoi_resolver::resolve::resolve_package_and_version(
409        source, None, true, yes
410    )?;
411    Ok(ResolvedPackage {
412        package,
413        version,
414        sharable_manifest,
415        source_path,
416        registry_handle,
417        repo_type,
418        git_sha
419    })
420}
421
422/// Resolves the dependency graph for one or more package sources.
423///
424/// # Errors
425///
426/// Returns an error if resolution fails.
427pub fn resolve_dependency_graph(
428    sources: &[String],
429    options: &DependencyResolutionOptions
430) -> Result<DependencyResolution> {
431    let (graph, non_zoi_dependencies) =
432        zoi_install::resolver::resolve_dependency_graph(
433            sources,
434            options.scope_override,
435            options.force,
436            options.yes,
437            options.all_optional,
438            options.build_type.as_deref(),
439            options.quiet,
440            None
441        )?;
442    Ok(DependencyResolution {
443        graph,
444        non_zoi_dependencies
445    })
446}
447
448/// Bundles a Zoi package and its local assets into a `.zsa` archive.
449///
450/// This function intelligently parses the `.pkg.lua` file to identify and
451/// include only the necessary local files.
452///
453/// # Errors
454///
455/// Returns an error if bundling fails.
456pub fn bundle_package(
457    package_file: &Path,
458    output_dir: Option<&Path>,
459    sign: Option<String>,
460    version_override: Option<&str>,
461    build_type: Option<&str>
462) -> Result<()> {
463    zoi_package::bundle::run(
464        package_file,
465        output_dir,
466        sign,
467        version_override,
468        build_type
469    )
470}
471
472/// Builds a Zoi package from a local `.pkg.lua` file.
473///
474/// This function reads a package definition, runs the build process, and
475/// creates a distributable `.zpa` archive.
476///
477/// # Arguments
478///
479/// * `package_file`: Path to the `.pkg.lua` file.
480/// * `build_type`: The type of package to build (e.g. "source",
481///   "pre-compiled").
482/// * `platforms`: A slice of platform strings to build for (e.g.
483///   `["linux-amd64"]`).
484/// * `sign_key`: An optional PGP key name or fingerprint to sign the package.
485///
486/// # Errors
487///
488/// Returns an error if the build process fails, if the package file cannot be
489/// read, or if the specified build type is not supported by the package.
490///
491/// # Examples
492///
493/// ```no_run
494/// use std::path::Path;
495///
496/// use anyhow::Result;
497/// use zoi::build;
498///
499/// fn main() -> Result<()> {
500///     let package_file = Path::new("my-package.pkg.lua");
501///     let platforms = vec!["linux-amd64".to_string()];
502///     build(
503///         package_file,
504///         Some("source"),
505///         &platforms,
506///         None,
507///         true,
508///         "native",
509///         None,
510///         None
511///     )?;
512///     println!("Package built successfully!");
513///     Ok(())
514/// }
515/// ```
516pub fn build(
517    package_file: &Path,
518    build_type: Option<&str>,
519    platforms: &[String],
520    sign_key: Option<String>,
521    install_deps: bool,
522    method: &str,
523    image: Option<&str>,
524    version_override: Option<&str>
525) -> Result<()> {
526    let options = BuildOptions {
527        build_type,
528        platforms: platforms.to_vec(),
529        sign_key,
530        output_dir: None,
531        sub_packages: None,
532        install_deps,
533        test: false,
534        method,
535        image,
536        version_override,
537        fakeroot: false
538    };
539    build_with_options(package_file, &options)
540}
541
542/// Installs a Zoi package from a local package archive.
543///
544/// This function unpacks a `.zpa` archive and installs its contents
545/// into the appropriate Zoi store, linking any binaries.
546///
547/// # Arguments
548///
549/// * `package_file`: Path to the local package archive.
550/// * *`scope_override`*: Optionally override the installation scope (`User`,
551///   `System`, `Project`).
552/// * `registry_handle`: The handle of the registry this package belongs to
553///   (e.g. "zoidberg", or "local").
554/// * `yes`: Automatically answer "yes" to any confirmation prompts (e.g. file
555///   conflicts).
556/// * `sub_packages`: For split packages, optionally specify which sub-packages
557///   to install.
558///
559/// # Returns
560///
561/// A `Result` containing a `Vec<String>` of all the file paths that were
562/// installed.
563///
564/// # Errors
565///
566/// Returns an error if the installation fails, such as if the archive is
567/// invalid or if there are file system permission issues.
568///
569/// # Examples
570///
571/// ```no_run
572/// use std::path::Path;
573///
574/// use anyhow::Result;
575/// use zoi::{Scope, install_package};
576///
577/// fn main() -> Result<()> {
578///     let archive_path = Path::new("my-package-1.0.0-linux-amd64.zpa");
579///     install_package(archive_path, Some(Scope::User), "local", true, None)?;
580///     println!("Package installed!");
581///     Ok(())
582/// }
583/// ```
584pub fn install_package(
585    package_file: &Path,
586    scope_override: Option<Scope>,
587    registry_handle: &str,
588    yes: bool,
589    sub_packages: Option<Vec<String>>
590) -> Result<Vec<String>> {
591    let options = PackageInstallOptions {
592        scope_override,
593        registry_handle: registry_handle.to_string(),
594        yes,
595        sub_packages,
596        link_bins: true
597    };
598    install_package_with_options(package_file, &options)
599}
600
601/// Uninstalls a Zoi package.
602///
603/// This function removes a package's files from the Zoi store and unlinks its
604/// binaries.
605///
606/// # Arguments
607///
608/// * `package_name`: The package identifier to uninstall. Use an explicit
609///   source like `#handle@repo/name[:sub]@version` when multiple installed
610///   packages share the same name.
611/// * `scope_override`: Optionally specify the scope to uninstall from. If
612///   `None`, Zoi will search for the package across all scopes.
613///
614/// # Errors
615///
616/// Returns an error if the package is not found or if the uninstallation
617/// process fails.
618///
619/// # Examples
620///
621/// ```no_run
622/// use anyhow::Result;
623/// use zoi::{Scope, uninstall_package};
624///
625/// fn main() -> Result<()> {
626///     uninstall_package("my-package", Some(Scope::User))?;
627///     println!("Package uninstalled!");
628///     Ok(())
629/// }
630/// ```
631pub fn uninstall_package(
632    package_name: &str,
633    scope_override: Option<Scope>
634) -> Result<()> {
635    let _lock = zoi_core::lock::acquire_lock()?;
636    zoi_uninstall::run(package_name, scope_override, false, false, false)
637        .map(|_| ())
638}