Compare commits
5 Commits
main
...
d10a6a9708
| Author | SHA1 | Date | |
|---|---|---|---|
| d10a6a9708 | |||
| 9524cae5ef | |||
| cdfdad5b8a | |||
| 7a4fef3ecf | |||
| 29e9005a18 |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
components: rustfmt
|
||||
- name: Check format
|
||||
run: cargo fmt --check
|
||||
- name: Install build dependencies
|
||||
@@ -32,12 +32,6 @@ jobs:
|
||||
sudo apt-get install -y pkg-config libssl-dev libgpg-error-dev libgpgme-dev
|
||||
- name: Build
|
||||
run: cargo build
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
- name: Lint
|
||||
run: cargo clippy --all-targets --all-features
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
- name: Install runtime system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
||||
@@ -24,10 +24,8 @@ Options:
|
||||
Commands and workflows include:
|
||||
```
|
||||
Commands:
|
||||
pull Pull a source package from the archive or git
|
||||
pull Get a source package from the archive or git
|
||||
chlog Auto-generate changelog entry, editing it, committing it afterwards
|
||||
build Build the source package (into a .dsc)
|
||||
deb Build the source package into binary package (.deb)
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
```
|
||||
|
||||
@@ -98,7 +96,7 @@ Missing features:
|
||||
- [ ] Three build modes:
|
||||
- [ ] Build locally (discouraged)
|
||||
- [x] Build using sbuild+unshare, with binary emulation (default)
|
||||
- [x] Cross-compilation
|
||||
- [ ] Cross-compilation
|
||||
- [ ] Async build
|
||||
- [ ] `pkh status`
|
||||
- [ ] Show build status
|
||||
|
||||
@@ -80,16 +80,10 @@ pub fn build(
|
||||
return Err("Could not install essential packages for the build".into());
|
||||
}
|
||||
|
||||
// Find the actual package directory
|
||||
let package_dir = crate::deb::find_package_directory(Path::new(build_root), package, version)?;
|
||||
let package_dir_str = package_dir
|
||||
.to_str()
|
||||
.ok_or("Invalid package directory path")?;
|
||||
|
||||
// Install build dependencies
|
||||
log::debug!("Installing build dependencies...");
|
||||
let mut cmd = ctx.command("apt-get");
|
||||
cmd.current_dir(package_dir_str)
|
||||
cmd.current_dir(format!("{build_root}/{package}"))
|
||||
.envs(env.clone())
|
||||
.arg("-y")
|
||||
.arg("build-dep");
|
||||
@@ -108,7 +102,7 @@ pub fn build(
|
||||
log::debug!("Building (debian/rules build) package...");
|
||||
let status = ctx
|
||||
.command("debian/rules")
|
||||
.current_dir(package_dir_str)
|
||||
.current_dir(format!("{build_root}/{package}"))
|
||||
.envs(env.clone())
|
||||
.arg("build")
|
||||
.status()?;
|
||||
@@ -119,7 +113,7 @@ pub fn build(
|
||||
// Run the 'binary' step to produce deb
|
||||
let status = ctx
|
||||
.command("fakeroot")
|
||||
.current_dir(package_dir_str)
|
||||
.current_dir(format!("{build_root}/{package}"))
|
||||
.envs(env.clone())
|
||||
.arg("debian/rules")
|
||||
.arg("binary")
|
||||
|
||||
@@ -85,45 +85,6 @@ pub fn build_binary_package(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find the current package directory by trying both patterns:
|
||||
/// - package/package
|
||||
/// - package/package-origversion
|
||||
pub(crate) fn find_package_directory(
|
||||
parent_dir: &Path,
|
||||
package: &str,
|
||||
version: &str,
|
||||
) -> Result<PathBuf, Box<dyn Error>> {
|
||||
let ctx = context::current();
|
||||
|
||||
// Try package/package pattern first
|
||||
let package_dir_pattern1 = parent_dir.join(package).join(package);
|
||||
if ctx.exists(&package_dir_pattern1)? {
|
||||
return Ok(package_dir_pattern1);
|
||||
}
|
||||
|
||||
// Compute origversion from version: remove everything after first '-', after stripping epoch
|
||||
let version_without_epoch = version.split_once(':').map(|(_, v)| v).unwrap_or(version);
|
||||
let origversion = version_without_epoch
|
||||
.split_once('-')
|
||||
.map(|(v, _)| v)
|
||||
.unwrap_or(version);
|
||||
|
||||
// Try package/package-origversion pattern
|
||||
let package_dir_pattern2 = parent_dir
|
||||
.join(package)
|
||||
.join(format!("{}-{}", package, origversion));
|
||||
if ctx.exists(&package_dir_pattern2)? {
|
||||
return Ok(package_dir_pattern2);
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"Could not find package directory for {} in {}",
|
||||
package,
|
||||
parent_dir.display()
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
fn find_dsc_file(
|
||||
build_root: &str,
|
||||
package: &str,
|
||||
@@ -164,17 +125,14 @@ mod tests {
|
||||
let cwd = temp_dir.path();
|
||||
log::debug!("Created temporary directory: {}", cwd.display());
|
||||
|
||||
log::info!("Pulling package {} from {}...", package, series);
|
||||
let package_info = crate::package_info::lookup(package, None, Some(series), "", dist, None)
|
||||
.await
|
||||
.expect("Cannot lookup package information");
|
||||
crate::pull::pull(&package_info, Some(cwd), None, true)
|
||||
log::info!("Pulling package {} from Ubuntu {}...", package, series);
|
||||
crate::pull::pull(package, "", Some(series), "", "", dist, Some(cwd), None)
|
||||
.await
|
||||
.expect("Cannot pull package");
|
||||
log::info!("Successfully pulled package {}", package);
|
||||
|
||||
// Change directory to the package directory
|
||||
let cwd = crate::deb::find_package_directory(cwd, package, &package_info.stanza.version).expect("Cannot find package directory");
|
||||
let cwd = cwd.join(package).join(package);
|
||||
log::debug!("Package directory: {}", cwd.display());
|
||||
|
||||
log::info!("Starting binary package build...");
|
||||
|
||||
@@ -2,26 +2,18 @@
|
||||
/// Call 'sbuild' with the dsc file to build the package with unshare
|
||||
use crate::context;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn build(
|
||||
package: &str,
|
||||
version: &str,
|
||||
_version: &str,
|
||||
arch: &str,
|
||||
series: &str,
|
||||
build_root: &str,
|
||||
cross: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let ctx = context::current();
|
||||
|
||||
// Find the actual package directory
|
||||
let package_dir = crate::deb::find_package_directory(Path::new(build_root), package, version)?;
|
||||
let package_dir_str = package_dir
|
||||
.to_str()
|
||||
.ok_or("Invalid package directory path")?;
|
||||
|
||||
let mut cmd = ctx.command("sbuild");
|
||||
cmd.current_dir(package_dir_str);
|
||||
cmd.current_dir(format!("{}/{}", build_root, package));
|
||||
cmd.arg("--chroot-mode=unshare");
|
||||
cmd.arg("--no-clean-source");
|
||||
|
||||
|
||||
35
src/main.rs
35
src/main.rs
@@ -7,6 +7,8 @@ use pkh::context::ContextConfig;
|
||||
|
||||
extern crate flate2;
|
||||
|
||||
use pkh::pull::pull;
|
||||
|
||||
use pkh::changelog::generate_entry;
|
||||
|
||||
use indicatif_log_bridge::LogWrapper;
|
||||
@@ -47,10 +49,10 @@ fn main() {
|
||||
.arg(arg!(--backport "This changelog is for a backport entry").required(false))
|
||||
.arg(arg!(-v --version <version> "Target version").required(false)),
|
||||
)
|
||||
.subcommand(Command::new("build").about("Build the source package (into a .dsc)"))
|
||||
.subcommand(Command::new("build").about("Build the source package"))
|
||||
.subcommand(
|
||||
Command::new("deb")
|
||||
.about("Build the source package into binary package (.deb)")
|
||||
.about("Build the binary package")
|
||||
.arg(arg!(-s --series <series> "Target distribution series").required(false))
|
||||
.arg(arg!(-a --arch <arch> "Target architecture").required(false))
|
||||
.arg(arg!(--cross "Cross-compile for target architecture (instead of qemu-binfmt)")
|
||||
@@ -92,8 +94,11 @@ fn main() {
|
||||
let package = sub_matches.get_one::<String>("package").expect("required");
|
||||
let series = sub_matches.get_one::<String>("series").map(|s| s.as_str());
|
||||
let dist = sub_matches.get_one::<String>("dist").map(|s| s.as_str());
|
||||
let version = sub_matches.get_one::<String>("version").map(|s| s.as_str());
|
||||
let _ppa = sub_matches
|
||||
let version = sub_matches
|
||||
.get_one::<String>("version")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("");
|
||||
let ppa = sub_matches
|
||||
.get_one::<String>("ppa")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("");
|
||||
@@ -101,18 +106,16 @@ fn main() {
|
||||
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(async {
|
||||
let package_info = pkh::package_info::lookup(
|
||||
package,
|
||||
version,
|
||||
series,
|
||||
"",
|
||||
dist,
|
||||
Some(&progress_callback),
|
||||
)
|
||||
.await?;
|
||||
pkh::pull::pull(&package_info, None, Some(&progress_callback), false).await
|
||||
}) {
|
||||
if let Err(e) = rt.block_on(pull(
|
||||
package,
|
||||
version,
|
||||
series,
|
||||
"",
|
||||
ppa,
|
||||
dist,
|
||||
None,
|
||||
Some(&progress_callback),
|
||||
)) {
|
||||
pb.finish_and_clear();
|
||||
error!("{}", e);
|
||||
std::process::exit(1);
|
||||
|
||||
@@ -56,8 +56,7 @@ fn parse_series_csv(content: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
Ok(entries.into_iter().map(|(s, _)| s).collect())
|
||||
}
|
||||
|
||||
/// Get time-ordered list of series for a distribution, development series first
|
||||
pub async fn get_ordered_series(dist: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
async fn get_ordered_series(dist: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let content = if Path::new(format!("/usr/share/distro-info/{dist}.csv").as_str()).exists() {
|
||||
std::fs::read_to_string(format!("/usr/share/distro-info/{dist}.csv"))?
|
||||
} else {
|
||||
@@ -72,8 +71,9 @@ pub async fn get_ordered_series(dist: &str) -> Result<Vec<String>, Box<dyn Error
|
||||
|
||||
let mut series = parse_series_csv(&content)?;
|
||||
|
||||
// For Debian, ensure 'sid' is first if it's not
|
||||
// We want to try 'sid' (unstable) first for Debian.
|
||||
// For Debian, ensure 'sid' is first if it's not (it usually doesn't have a date or is very old/new depending on file)
|
||||
// Actually in the file sid has 1993 date.
|
||||
// But we want to try 'sid' (unstable) first for Debian.
|
||||
if dist == "debian" {
|
||||
series.retain(|s| s != "sid");
|
||||
series.insert(0, "sid".to_string());
|
||||
@@ -332,7 +332,7 @@ fn parse_sources(
|
||||
}
|
||||
|
||||
/// Get package information from a package, distribution series, and pocket
|
||||
async fn get(
|
||||
pub async fn get(
|
||||
package_name: &str,
|
||||
series: &str,
|
||||
pocket: &str,
|
||||
@@ -406,7 +406,7 @@ async fn get(
|
||||
}
|
||||
|
||||
/// Try to find package information in a distribution, trying all series and pockets
|
||||
async fn find_package(
|
||||
pub async fn find_package(
|
||||
package_name: &str,
|
||||
dist: &str,
|
||||
pocket: &str,
|
||||
@@ -452,58 +452,6 @@ async fn find_package(
|
||||
Err(format!("Package '{}' not found.", package_name).into())
|
||||
}
|
||||
|
||||
/// Lookup package information for a source package
|
||||
///
|
||||
/// This function obtains package information either directly from a specific series
|
||||
/// or by searching across all series in a distribution.
|
||||
pub async fn lookup(
|
||||
package: &str,
|
||||
version: Option<&str>,
|
||||
series: Option<&str>,
|
||||
pocket: &str,
|
||||
dist: Option<&str>,
|
||||
progress: ProgressCallback<'_>,
|
||||
) -> Result<PackageInfo, Box<dyn Error>> {
|
||||
// Obtain the package information, either directly in a series or with a search in all series
|
||||
let package_info = if let Some(s) = series {
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Resolving package info for {}...", package),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the package information from that series and pocket
|
||||
get(package, s, pocket, version).await?
|
||||
} else {
|
||||
let dist = dist.unwrap_or_else(||
|
||||
// Use auto-detection to see if current distro is ubuntu, or fallback to debian by default
|
||||
if std::process::Command::new("lsb_release").arg("-i").arg("-s").output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_lowercase()).unwrap_or_default() == "ubuntu" {
|
||||
"ubuntu"
|
||||
} else {
|
||||
"debian"
|
||||
}
|
||||
);
|
||||
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Searching for package {} in {}...", package, dist),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Try to find the package in all series from that dist
|
||||
find_package(package, dist, pocket, version, progress).await?
|
||||
};
|
||||
|
||||
Ok(package_info)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
90
src/pull.rs
90
src/pull.rs
@@ -2,6 +2,7 @@ use std::cmp::min;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::package_info;
|
||||
use crate::package_info::PackageInfo;
|
||||
|
||||
use std::process::Command;
|
||||
@@ -332,19 +333,64 @@ async fn fetch_archive_sources(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pull a source package locally using pre-retrieved package information
|
||||
/// Pull a source package locally
|
||||
///
|
||||
/// This function takes a PackageInfo struct and downloads the package using the preferred method
|
||||
/// (either git or direct archive download), as well as orig tarball, inside 'package' directory.
|
||||
/// The source will be extracted under 'package/package'.
|
||||
/// 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_info: &PackageInfo,
|
||||
package: &str,
|
||||
_version: &str,
|
||||
series: Option<&str>,
|
||||
pocket: &str,
|
||||
_ppa: &str,
|
||||
dist: Option<&str>,
|
||||
cwd: Option<&Path>,
|
||||
progress: ProgressCallback<'_>,
|
||||
force_archive: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let package = &package_info.stanza.package;
|
||||
let series = &package_info.series;
|
||||
) -> Result<PackageInfo, Box<dyn Error>> {
|
||||
let version_opt = if _version.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(_version)
|
||||
};
|
||||
|
||||
/* Obtain the package information, either directly in a series or with a search in all series */
|
||||
let package_info = if let Some(s) = series {
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Resolving package info for {}...", package),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the package information from that series and pocket
|
||||
package_info::get(package, s, pocket, version_opt).await?
|
||||
} else {
|
||||
let dist = dist.unwrap_or_else(||
|
||||
// Use auto-detection to see if current distro is ubuntu, or fallback to debian by default
|
||||
if std::process::Command::new("lsb_release").arg("-i").arg("-s").output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_lowercase()).unwrap_or_default() == "ubuntu" {
|
||||
"ubuntu"
|
||||
} else {
|
||||
"debian"
|
||||
}
|
||||
);
|
||||
|
||||
if let Some(cb) = progress {
|
||||
cb(
|
||||
&format!("Searching for package {} in {}...", package, dist),
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Try to find the package in all series from that dist
|
||||
package_info::find_package(package, dist, pocket, version_opt, progress).await?
|
||||
};
|
||||
|
||||
let package_dir = if let Some(path) = cwd {
|
||||
path.join(package)
|
||||
} else {
|
||||
@@ -352,20 +398,15 @@ pub async fn pull(
|
||||
};
|
||||
|
||||
/* Fetch the package: either via git (preferred VCS) or the archive */
|
||||
if let Some(ref url) = package_info.preferred_vcs
|
||||
&& !force_archive
|
||||
{
|
||||
if let Some(ref url) = package_info.preferred_vcs {
|
||||
// We have found a preferred VCS (git repository) for the package, so
|
||||
// we fetch the package from that repo.
|
||||
|
||||
// Depending on target series, we pick target branch; if latest series is specified,
|
||||
// Depending on target series, we pick target branch; if no series is specified,
|
||||
// we target the development branch, i.e. the default branch
|
||||
let branch_name = if crate::package_info::get_ordered_series(package_info.dist.as_str())
|
||||
.await?[0]
|
||||
!= *series
|
||||
{
|
||||
let branch_name = if let Some(s) = series {
|
||||
if package_info.dist == "ubuntu" {
|
||||
Some(format!("{}/{}", package_info.dist, series))
|
||||
Some(format!("{}/{}", package_info.dist, s))
|
||||
} else {
|
||||
// Debian does not have reliable branch naming...
|
||||
// For now, we skip that part and clone default
|
||||
@@ -405,7 +446,7 @@ pub async fn pull(
|
||||
if let Some(cb) = progress {
|
||||
cb("Fetching orig tarball...", "", 0, 0);
|
||||
}
|
||||
fetch_orig_tarball(package_info, Some(&package_dir), progress).await?;
|
||||
fetch_orig_tarball(&package_info, Some(&package_dir), progress).await?;
|
||||
} else {
|
||||
debug!("Native package, skipping orig tarball fetch.");
|
||||
}
|
||||
@@ -413,16 +454,16 @@ pub async fn pull(
|
||||
if let Some(cb) = progress {
|
||||
cb("Fetching dsc file...", "", 0, 0);
|
||||
}
|
||||
fetch_dsc_file(package_info, Some(&package_dir), progress).await?;
|
||||
fetch_dsc_file(&package_info, Some(&package_dir), progress).await?;
|
||||
} else {
|
||||
// Fallback to archive fetching
|
||||
if let Some(cb) = progress {
|
||||
cb("Downloading from archive...", "", 0, 0);
|
||||
}
|
||||
fetch_archive_sources(package_info, Some(&package_dir), progress).await?;
|
||||
fetch_archive_sources(&package_info, Some(&package_dir), progress).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(package_info)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -434,17 +475,16 @@ mod tests {
|
||||
|
||||
// For determinism, we require for tests that either a distro or series is specified,
|
||||
// as no distribution would mean fallback to system distro
|
||||
assert!(dist.is_some() || series.is_some());
|
||||
assert!(dist != None || series != None);
|
||||
|
||||
// Use a temp directory as working directory
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let cwd = temp_dir.path();
|
||||
|
||||
// Main 'pull' command: the one we want to test
|
||||
let info = crate::package_info::lookup(package, None, series, "", dist, None)
|
||||
let info = pull(package, "", series, "", "", dist, Some(cwd), None)
|
||||
.await
|
||||
.unwrap();
|
||||
pull(&info, Some(cwd), None, false).await.unwrap();
|
||||
|
||||
let package_dir = cwd.join(package);
|
||||
assert!(package_dir.exists());
|
||||
|
||||
Reference in New Issue
Block a user