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 ¤t_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}