Skip to main content

bob/
lib.rs

1/*
2 * Copyright (c) 2026 Jonathan Perkin <jonathan@perkin.org.uk>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
18
19pub mod action;
20pub mod build;
21pub mod config;
22pub mod db;
23pub mod report;
24pub mod sandbox;
25pub mod scan;
26pub mod summary;
27
28// Internal modules - exposed for binary use but not primary API
29mod init;
30pub mod logging;
31mod tui;
32
33use std::collections::{HashMap, VecDeque};
34use std::hash::Hash;
35use std::io::{self, Write};
36use std::sync::Arc;
37use std::sync::atomic::AtomicBool;
38
39/**
40 * Return all packages in build priority order.
41 *
42 * Packages that unblock the most downstream work appear first.
43 * `deps` maps each package to its dependencies; all packages must
44 * be keys. `weight` provides per-package base weight.
45 */
46pub fn build_order<K>(deps: &HashMap<K, Vec<K>>, weight: impl Fn(&K) -> usize) -> Vec<K>
47where
48    K: Eq + Hash + Clone + Ord,
49{
50    let mut rev: HashMap<&K, Vec<&K>> = HashMap::new();
51    for pkg in deps.keys() {
52        rev.entry(pkg).or_default();
53    }
54    for (pkg, d) in deps {
55        for dep in d {
56            rev.entry(dep).or_default().push(pkg);
57        }
58    }
59    let mut pending: HashMap<&K, usize> = deps.keys().map(|p| (p, rev[p].len())).collect();
60    let mut queue: VecDeque<&K> = pending
61        .iter()
62        .filter(|(_, c)| **c == 0)
63        .map(|(&p, _)| p)
64        .collect();
65    let mut weights: HashMap<&K, usize> = HashMap::new();
66    while let Some(pkg) = queue.pop_front() {
67        let w = rev[pkg]
68            .iter()
69            .fold(weight(pkg), |a, d| a + weights.get(d).copied().unwrap_or(0));
70        weights.insert(pkg, w);
71        for dep in deps[pkg].iter() {
72            if let Some(c) = pending.get_mut(dep) {
73                *c -= 1;
74                if *c == 0 {
75                    queue.push_back(dep);
76                }
77            }
78        }
79    }
80    let mut result: Vec<K> = deps.keys().cloned().collect();
81    result.sort_by(|a, b| weights.get(b).cmp(&weights.get(a)).then_with(|| a.cmp(b)));
82    result
83}
84
85/**
86 * Write a line to stdout, returning false on broken pipe.
87 *
88 * Use this in loops to gracefully handle SIGPIPE (e.g., when piped to `head`).
89 */
90pub fn try_println(s: &str) -> bool {
91    let result = writeln!(io::stdout(), "{}", s);
92    !matches!(result, Err(e) if e.kind() == io::ErrorKind::BrokenPipe)
93}
94
95/// Error indicating the operation was interrupted (e.g., by Ctrl+C).
96#[derive(Debug)]
97pub struct Interrupted;
98
99impl std::fmt::Display for Interrupted {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        write!(f, "Interrupted")
102    }
103}
104
105impl std::error::Error for Interrupted {}
106
107/// Shared context for a build or scan run.
108#[derive(Clone, Debug)]
109pub struct RunContext {
110    /// Flag to signal graceful shutdown.
111    pub shutdown: Arc<AtomicBool>,
112}
113
114impl RunContext {
115    /**
116     * Create a new run context with the given shutdown flag.
117     *
118     * The shutdown flag is shared across all threads. Set it to `true`
119     * to trigger graceful shutdown of any running scan or build.
120     */
121    pub fn new(shutdown: Arc<AtomicBool>) -> Self {
122        Self { shutdown }
123    }
124}
125
126// Re-export main types for convenience.
127//
128// The typical workflow is:
129//   Config::load() → Scan::new() → scan.start() → scan.resolve()
130//   → Build::new() → build.start() → write_html_report()
131
132pub use action::{Action, ActionType, FSType};
133pub use build::{
134    Build, BuildCounts, BuildOutcome, BuildReason, BuildResult, BuildSummary, pkg_up_to_date,
135};
136pub use config::{Config, Options, Pkgsrc, PkgsrcEnv, Sandboxes};
137pub use db::Database;
138pub use init::Init;
139pub use report::write_html_report;
140pub use sandbox::Sandbox;
141pub use scan::{ResolvedPackage, Scan, ScanResult, ScanSummary, SkipReason, SkippedCounts};
142pub use summary::generate_pkg_summary;