docs: added documentation, enforced documentation
All checks were successful
CI / build (push) Successful in 7m21s
All checks were successful
CI / build (push) Successful in 7m21s
This commit is contained in:
@@ -2,6 +2,7 @@ use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Build a Debian source package (to a .dsc)
|
||||
pub fn build_source_package(cwd: Option<&Path>) -> Result<(), Box<dyn Error>> {
|
||||
let cwd = cwd.unwrap_or_else(|| Path::new("."));
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::fs::File;
|
||||
use std::io::{self, BufRead, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
/*
|
||||
* Automatically generate a changelog entry from a commit history and previous changelog
|
||||
*/
|
||||
/// Automatically generate a changelog entry from a commit history and previous changelog
|
||||
pub fn generate_entry(
|
||||
changelog_file: &str,
|
||||
cwd: Option<&Path>,
|
||||
@@ -61,10 +59,8 @@ pub fn generate_entry(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the next (most probable) version number of a package, from old version and
|
||||
* conditions on changes (is ubuntu upload, is a no change rebuild, is a non-maintainer upload)
|
||||
*/
|
||||
/// Compute the next (most probable) version number of a package, from old version and
|
||||
/// conditions on changes (is ubuntu upload, is a no change rebuild, is a non-maintainer upload)
|
||||
fn compute_new_version(
|
||||
old_version: &str,
|
||||
is_ubuntu: bool,
|
||||
@@ -87,9 +83,7 @@ fn compute_new_version(
|
||||
increment_suffix(old_version, "")
|
||||
}
|
||||
|
||||
/*
|
||||
* Increment a version number by 1, for a given suffix
|
||||
*/
|
||||
/// Increment a version number by 1, for a given suffix
|
||||
fn increment_suffix(version: &str, suffix: &str) -> String {
|
||||
// If suffix is empty, we just look for trailing digits
|
||||
// If suffix is not empty, we look for suffix followed by digits
|
||||
@@ -120,9 +114,7 @@ fn increment_suffix(version: &str, suffix: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a changelog file first entry header, to obtain (package, version, series)
|
||||
*/
|
||||
/// Parse a changelog file first entry header, to obtain (package, version, series)
|
||||
pub fn parse_changelog_header(
|
||||
path: &Path,
|
||||
) -> Result<(String, String, String), Box<dyn std::error::Error>> {
|
||||
|
||||
@@ -10,6 +10,7 @@ use super::schroot::SchrootDriver;
|
||||
use super::ssh::SshDriver;
|
||||
use super::unshare::UnshareDriver;
|
||||
|
||||
/// A ContextDriver is the interface for the logic happening inside a context
|
||||
pub trait ContextDriver {
|
||||
fn ensure_available(&self, src: &Path, dest_root: &str) -> io::Result<PathBuf>;
|
||||
fn retrieve_path(&self, src: &Path, dest: &Path) -> io::Result<()>;
|
||||
@@ -41,34 +42,52 @@ pub trait ContextDriver {
|
||||
#[serde(tag = "type")]
|
||||
#[derive(Default)]
|
||||
pub enum ContextConfig {
|
||||
/// Local context: actions executed locally
|
||||
#[serde(rename = "local")]
|
||||
#[default]
|
||||
Local,
|
||||
/// SSH context: actions over an SSH connection
|
||||
#[serde(rename = "ssh")]
|
||||
Ssh {
|
||||
/// Host for the SSH connection
|
||||
host: String,
|
||||
/// User for the SSH connection
|
||||
user: Option<String>,
|
||||
/// TCP port for the SSH connection
|
||||
port: Option<u16>,
|
||||
},
|
||||
/// Schroot context: using `schroot`
|
||||
#[serde(rename = "schroot")]
|
||||
Schroot {
|
||||
/// Name of the schroot
|
||||
name: String,
|
||||
/// Optional parent context for the Schroot context
|
||||
parent: Option<String>,
|
||||
},
|
||||
/// Unshare context: chroot with dropped permissions (using `unshare`)
|
||||
#[serde(rename = "unshare")]
|
||||
Unshare {
|
||||
/// Path to use for chrooting
|
||||
path: String,
|
||||
/// Optional parent context for the Unshare context
|
||||
parent: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// A context, allowing to run commands, read and write files, etc
|
||||
pub struct Context {
|
||||
/// Configuration for the context
|
||||
pub config: ContextConfig,
|
||||
/// Parent context for the context
|
||||
///
|
||||
/// For example, you could have a chroot context over an ssh connection
|
||||
pub parent: Option<Arc<Context>>,
|
||||
/// ContextDriver for the context, implementing the logic for actions
|
||||
driver: Mutex<Option<Box<dyn ContextDriver + Send + Sync>>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Create a context from configuration
|
||||
pub fn new(config: ContextConfig) -> Self {
|
||||
let parent = match &config {
|
||||
ContextConfig::Schroot {
|
||||
@@ -97,6 +116,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a context with an explicit parent context
|
||||
pub fn with_parent(config: ContextConfig, parent: Arc<Context>) -> Self {
|
||||
Self {
|
||||
config,
|
||||
@@ -105,6 +125,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a command inside context
|
||||
pub fn command<S: AsRef<OsStr>>(&self, program: S) -> ContextCommand<'_> {
|
||||
ContextCommand {
|
||||
context: self,
|
||||
@@ -126,6 +147,7 @@ impl Context {
|
||||
.ensure_available(src, dest_root)
|
||||
}
|
||||
|
||||
/// Create a temp directory inside context
|
||||
pub fn create_temp_dir(&self) -> io::Result<String> {
|
||||
self.driver().as_ref().unwrap().create_temp_dir()
|
||||
}
|
||||
@@ -143,18 +165,22 @@ impl Context {
|
||||
self.driver().as_ref().unwrap().list_files(path)
|
||||
}
|
||||
|
||||
/// Copy a path inside context
|
||||
pub fn copy_path(&self, src: &Path, dest: &Path) -> io::Result<()> {
|
||||
self.driver().as_ref().unwrap().copy_path(src, dest)
|
||||
}
|
||||
|
||||
/// Read a file inside context
|
||||
pub fn read_file(&self, path: &Path) -> io::Result<String> {
|
||||
self.driver().as_ref().unwrap().read_file(path)
|
||||
}
|
||||
|
||||
/// Write a file inside context
|
||||
pub fn write_file(&self, path: &Path, content: &str) -> io::Result<()> {
|
||||
self.driver().as_ref().unwrap().write_file(path, content)
|
||||
}
|
||||
|
||||
/// Create and obtain a specific driver for the context
|
||||
pub fn driver(
|
||||
&self,
|
||||
) -> std::sync::MutexGuard<'_, Option<Box<dyn ContextDriver + Send + Sync>>> {
|
||||
@@ -182,6 +208,7 @@ impl Context {
|
||||
driver_lock
|
||||
}
|
||||
|
||||
/// Clone a context
|
||||
pub fn clone_raw(&self) -> Self {
|
||||
Self {
|
||||
config: self.config.clone(),
|
||||
@@ -207,12 +234,13 @@ pub struct ContextCommand<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ContextCommand<'a> {
|
||||
/// Add an argument to current command
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
||||
self.args.push(arg.as_ref().to_string_lossy().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
// Support chaining args
|
||||
/// Add multiple command arguments
|
||||
pub fn args<I, S>(&mut self, args: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
@@ -224,6 +252,7 @@ impl<'a> ContextCommand<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set environment variable for command
|
||||
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
|
||||
where
|
||||
K: AsRef<OsStr>,
|
||||
@@ -236,6 +265,7 @@ impl<'a> ContextCommand<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set multiple environment variables for command
|
||||
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
@@ -248,11 +278,13 @@ impl<'a> ContextCommand<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set current working directory for command
|
||||
pub fn current_dir<P: AsRef<OsStr>>(&mut self, dir: P) -> &mut Self {
|
||||
self.cwd = Some(dir.as_ref().to_string_lossy().to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Run command and obtain exit status
|
||||
pub fn status(&mut self) -> io::Result<std::process::ExitStatus> {
|
||||
self.context.driver().as_ref().unwrap().run(
|
||||
&self.program,
|
||||
@@ -262,7 +294,7 @@ impl<'a> ContextCommand<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
// Capture output
|
||||
/// Run command, capturing output
|
||||
pub fn output(&mut self) -> io::Result<std::process::Output> {
|
||||
self.context.driver().as_ref().unwrap().run_output(
|
||||
&self.program,
|
||||
|
||||
@@ -26,6 +26,7 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper managing contexts
|
||||
pub struct ContextManager {
|
||||
context: RwLock<Arc<Context>>,
|
||||
config_path: PathBuf,
|
||||
@@ -67,10 +68,12 @@ impl ContextManager {
|
||||
})
|
||||
}
|
||||
|
||||
/// Obtain current ContextManager configuration
|
||||
pub fn get_config(&self) -> std::sync::RwLockReadGuard<'_, Config> {
|
||||
self.config.read().unwrap()
|
||||
}
|
||||
|
||||
/// Make a ContextManager using a specific configuration path
|
||||
pub fn with_path(path: PathBuf) -> Self {
|
||||
let config = Config::default();
|
||||
Self {
|
||||
@@ -80,6 +83,7 @@ impl ContextManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Save current context configuration to disk
|
||||
pub fn save(&self) -> io::Result<()> {
|
||||
let config = self.config.read().unwrap();
|
||||
let content = serde_json::to_string_pretty(&*config)
|
||||
@@ -97,6 +101,7 @@ impl ContextManager {
|
||||
Context::new(context_config)
|
||||
}
|
||||
|
||||
/// List contexts from configuration
|
||||
pub fn list_contexts(&self) -> Vec<String> {
|
||||
self.config
|
||||
.read()
|
||||
@@ -107,6 +112,7 @@ impl ContextManager {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Add a context to configuration
|
||||
pub fn add_context(&self, name: &str, config: ContextConfig) -> io::Result<()> {
|
||||
self.config
|
||||
.write()
|
||||
@@ -116,6 +122,7 @@ impl ContextManager {
|
||||
self.save()
|
||||
}
|
||||
|
||||
/// Remove context from configuration
|
||||
pub fn remove_context(&self, name: &str) -> io::Result<()> {
|
||||
let mut config = self.config.write().unwrap();
|
||||
if name == "local" {
|
||||
@@ -137,6 +144,7 @@ impl ContextManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set current context from name (modifying configuration)
|
||||
pub fn set_current(&self, name: &str) -> io::Result<()> {
|
||||
let mut config = self.config.write().unwrap();
|
||||
if config.contexts.contains_key(name) {
|
||||
@@ -153,14 +161,18 @@ impl ContextManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set current context, without modifying configuration
|
||||
pub fn set_current_ephemeral(&self, context: Context) {
|
||||
*self.context.write().unwrap() = context.into();
|
||||
}
|
||||
|
||||
/// Obtain current context handle
|
||||
pub fn current(&self) -> Arc<Context> {
|
||||
self.context.read().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Obtain current context name
|
||||
/// Will not work for ephemeral context (obtained from config)
|
||||
pub fn current_name(&self) -> String {
|
||||
self.config.read().unwrap().context.clone()
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ pub use api::{Context, ContextCommand, ContextConfig};
|
||||
pub use manager::ContextManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Obtain global context manager
|
||||
pub fn manager() -> &'static ContextManager {
|
||||
&manager::MANAGER
|
||||
}
|
||||
|
||||
/// Obtain current context
|
||||
pub fn current() -> Arc<Context> {
|
||||
manager::MANAGER.current()
|
||||
}
|
||||
|
||||
@@ -6,12 +6,16 @@ use crate::context;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Build mode for the binary build
|
||||
#[derive(PartialEq)]
|
||||
pub enum BuildMode {
|
||||
/// Use `sbuild` for the build, configured in unshare mode
|
||||
Sbuild,
|
||||
/// Local build, directly on the context
|
||||
Local,
|
||||
}
|
||||
|
||||
/// Build package in 'cwd' to a .deb
|
||||
pub fn build_binary_package(
|
||||
arch: Option<&str>,
|
||||
series: Option<&str>,
|
||||
|
||||
19
src/lib.rs
19
src/lib.rs
@@ -1,10 +1,27 @@
|
||||
//! pkh: Debian packaging helper
|
||||
//!
|
||||
//! pkh allows working with Debian packages, with multiple actions/submodules
|
||||
#![deny(missing_docs)]
|
||||
|
||||
/// Build a Debian source package (into a .dsc)
|
||||
pub mod build;
|
||||
/// Parse or edit a Debian changelog of a source package
|
||||
pub mod changelog;
|
||||
pub mod context;
|
||||
/// Build a Debian package into a binary (.deb)
|
||||
pub mod deb;
|
||||
/// Obtain information about one or multiple packages
|
||||
pub mod package_info;
|
||||
/// Download a source package locally
|
||||
pub mod pull;
|
||||
|
||||
/// Handle context for .deb building: locally, over ssh, in a chroot...
|
||||
pub mod context;
|
||||
|
||||
/// Optional callback function (taking 4 arguments)
|
||||
/// - Name of the current main operation (e.g. pulling package)
|
||||
/// - Name of the current nested operation (e.g. cloning git repo)
|
||||
/// - Progress, position, index of current operation (e.g. amount of data downloaded)
|
||||
/// - Total amount for current operation (e.g. size of the file to download)
|
||||
pub type ProgressCallback<'a> = Option<&'a dyn Fn(&str, &str, usize, usize)>;
|
||||
|
||||
/// Returns the architecture of current CPU, debian-compatible
|
||||
|
||||
@@ -103,9 +103,9 @@ fn main() {
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
// Since pull is async, we need to block on it
|
||||
let (pb, progress_callback) = ui::create_progress_bar(&multi);
|
||||
|
||||
// Since pull is async, we need to block on it
|
||||
if let Err(e) = rt.block_on(pull(
|
||||
package,
|
||||
version,
|
||||
|
||||
@@ -93,6 +93,7 @@ fn get_series_from_file(path: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
parse_series_csv(&content)
|
||||
}
|
||||
|
||||
/// Obtain a list of series from a distribution
|
||||
pub async fn get_dist_series(dist: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
if Path::new(format!("/usr/share/distro-info/{dist}.csv").as_str()).exists() {
|
||||
get_series_from_file(format!("/usr/share/distro-info/{dist}.csv").as_str())
|
||||
@@ -105,6 +106,7 @@ pub async fn get_dist_series(dist: &str) -> Result<Vec<String>, Box<dyn Error>>
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the distribution (eg. debian, ubuntu) from a distribution series (eg. noble, bookworm)
|
||||
pub async fn get_dist_from_series(series: &str) -> Result<String, Box<dyn Error>> {
|
||||
let debian_series = get_dist_series("debian").await?;
|
||||
if debian_series.contains(&series.to_string()) {
|
||||
@@ -117,34 +119,55 @@ pub async fn get_dist_from_series(series: &str) -> Result<String, Box<dyn Error>
|
||||
Err(format!("Unknown series: {}", series).into())
|
||||
}
|
||||
|
||||
/// A File used in a source package
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileEntry {
|
||||
/// Name of the file
|
||||
pub name: String,
|
||||
/// Size of the file
|
||||
pub size: u64,
|
||||
/// SHA256 hash for the file
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
/// A package 'stanza' as found is 'Sources.gz' files, containing basic information about a source package
|
||||
#[derive(Debug)]
|
||||
pub struct PackageStanza {
|
||||
/// Name of the package
|
||||
pub package: String,
|
||||
/// Version number for the package
|
||||
pub version: String,
|
||||
/// Directory field in the stanza
|
||||
pub directory: String,
|
||||
/// Source package format (e.g. '3.0 (quilt)')
|
||||
pub format: String,
|
||||
/// Vcs-Git field in the stanza
|
||||
pub vcs_git: Option<String>,
|
||||
/// Vcs-Browser field in the stanza
|
||||
pub vcs_browser: Option<String>,
|
||||
/// Files present in the source package
|
||||
pub files: Vec<FileEntry>,
|
||||
}
|
||||
|
||||
/// Source package information
|
||||
#[derive(Debug)]
|
||||
pub struct PackageInfo {
|
||||
pub dist: String,
|
||||
pub series: String,
|
||||
/// Source 'stanza' for the package, containing basic information
|
||||
pub stanza: PackageStanza,
|
||||
/// Distribution for the package
|
||||
pub dist: String,
|
||||
/// Distribution series for the package
|
||||
pub series: String,
|
||||
/// Preferred VCS for the source package
|
||||
///
|
||||
/// Should be Launchpad on Ubuntu, and Salsa on Debian
|
||||
pub preferred_vcs: Option<String>,
|
||||
/// URL for the files of the source package
|
||||
pub archive_url: String,
|
||||
}
|
||||
|
||||
impl PackageInfo {
|
||||
/// Returns true if the package is a Debian native package (no orig)
|
||||
pub fn is_native(&self) -> bool {
|
||||
self.stanza.format.contains("(native)")
|
||||
}
|
||||
@@ -175,9 +198,7 @@ fn get_base_url(dist: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain the URL for the 'Release' file of a distribution series
|
||||
*/
|
||||
/// Obtain the URL for the 'Release' file of a distribution series
|
||||
fn get_release_url(base_url: &str, series: &str, pocket: &str) -> String {
|
||||
let pocket_full = if pocket.is_empty() {
|
||||
String::new()
|
||||
@@ -187,9 +208,7 @@ fn get_release_url(base_url: &str, series: &str, pocket: &str) -> String {
|
||||
format!("{base_url}/dists/{series}{pocket_full}/Release")
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain the components of a distribution series by parsing the 'Release' file
|
||||
*/
|
||||
/// Obtain the components of a distribution series by parsing the 'Release' file
|
||||
async fn get_components(
|
||||
base_url: &str,
|
||||
series: &str,
|
||||
@@ -296,10 +315,8 @@ impl Iterator for DebianSources {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a 'Sources.gz' debian package file data, to look for a target package and
|
||||
* return the data for that package stanza
|
||||
*/
|
||||
/// Parse a 'Sources.gz' debian package file data, to look for a target package and
|
||||
/// return the data for that package stanza
|
||||
fn parse_sources(
|
||||
data: &[u8],
|
||||
target_package: &str,
|
||||
@@ -314,6 +331,7 @@ fn parse_sources(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get package information from a package, distribution series, and pocket
|
||||
pub async fn get(
|
||||
package_name: &str,
|
||||
series: &str,
|
||||
@@ -387,6 +405,7 @@ pub async fn get(
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Try to find package information in a distribution, trying all series and pockets
|
||||
pub async fn find_package(
|
||||
package_name: &str,
|
||||
dist: &str,
|
||||
|
||||
@@ -333,6 +333,11 @@ async fn fetch_archive_sources(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pull a source package locally
|
||||
///
|
||||
/// Will try to find the package information, and use it to download it over prefered way
|
||||
/// (either git or direct archive download), as well as orig tarball, inside 'package' directory
|
||||
/// The source will be extracted under 'package/package'
|
||||
pub async fn pull(
|
||||
package: &str,
|
||||
_version: &str,
|
||||
|
||||
Reference in New Issue
Block a user