Compare commits
2
Commits
348abf61b9
...
c7af3bc9b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7af3bc9b1 | ||
|
|
182a06ffbe |
+72
-29
@@ -7,7 +7,7 @@ use crate::ui::logfmt::QuiltClassifier;
|
|||||||
use log::warn;
|
use log::warn;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::apt;
|
use crate::apt;
|
||||||
@@ -37,7 +37,8 @@ pub async fn build(
|
|||||||
inject_packages: Option<&[&str]>,
|
inject_packages: Option<&[&str]>,
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
ui: Option<Arc<DebUi>>,
|
ui: Option<Arc<DebUi>>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
jobs: Option<usize>,
|
||||||
|
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||||
let sink: Option<Arc<dyn LineSink>> = ui.as_ref().map(|u| u.sink());
|
let sink: Option<Arc<dyn LineSink>> = ui.as_ref().map(|u| u.sink());
|
||||||
|
|
||||||
// Environment
|
// Environment
|
||||||
@@ -45,21 +46,25 @@ pub async fn build(
|
|||||||
env.insert("LANG".to_string(), "C".to_string());
|
env.insert("LANG".to_string(), "C".to_string());
|
||||||
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
env.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string());
|
||||||
|
|
||||||
// Parallel building: find local number of cores, and use that
|
// Parallel building: honor an explicit -j/--jobs count, otherwise detect
|
||||||
let num_cores = ctx
|
// the number of cores available inside the build context (nproc).
|
||||||
.command("nproc")
|
let num_cores = match jobs {
|
||||||
.output()
|
Some(j) => j,
|
||||||
.map(|output| {
|
None => ctx
|
||||||
if output.status.success() {
|
.command("nproc")
|
||||||
String::from_utf8_lossy(&output.stdout)
|
.output()
|
||||||
.trim()
|
.map(|output| {
|
||||||
.parse::<usize>()
|
if output.status.success() {
|
||||||
.unwrap_or(1)
|
String::from_utf8_lossy(&output.stdout)
|
||||||
} else {
|
.trim()
|
||||||
1 // Default to 1 if nproc fails
|
.parse::<usize>()
|
||||||
}
|
.unwrap_or(1)
|
||||||
})
|
} else {
|
||||||
.unwrap_or(1); // Default to 1 if we can't execute the command
|
1 // Default to 1 if nproc fails
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(1), // Default to 1 if we can't execute the command
|
||||||
|
};
|
||||||
|
|
||||||
// Build options: parallel, disable tests by default
|
// Build options: parallel, disable tests by default
|
||||||
env.insert(
|
env.insert(
|
||||||
@@ -329,15 +334,60 @@ pub async fn build(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect the exact set of artifacts produced by this build so the caller
|
||||||
|
// retrieves only those: the binary packages registered in debian/files
|
||||||
|
// (the contract between dh_builddeb/dpkg-gencontrol and the artifact
|
||||||
|
// generators) plus the .buildinfo/.changes generated below. This avoids
|
||||||
|
// globbing the build root, which would also surface stale files copied
|
||||||
|
// alongside the package tree.
|
||||||
|
let mut artifacts = collect_binary_artifacts(&ctx, package_dir_str, build_root)?;
|
||||||
|
|
||||||
// Generate the upload metadata (.buildinfo + .changes) natively, the
|
// Generate the upload metadata (.buildinfo + .changes) natively, the
|
||||||
// equivalent of dpkg-genbuildinfo -b + dpkg-genchanges -b, consuming
|
// equivalent of dpkg-genbuildinfo -b + dpkg-genchanges -b, consuming
|
||||||
// debian/files produced by the build. Failures are logged but do not
|
// debian/files produced by the build. Failures are logged but do not
|
||||||
// discard the produced binaries.
|
// discard the produced binaries.
|
||||||
if let Err(e) = generate_upload_metadata(package_dir_str, build_root, arch, cross, &env, &ctx) {
|
match generate_upload_metadata(package_dir_str, build_root, arch, cross, &env, &ctx) {
|
||||||
warn!("failed to generate .buildinfo/.changes: {}", e);
|
Ok((buildinfo, changes)) => {
|
||||||
|
log::info!(
|
||||||
|
"generated upload metadata: {} and {}",
|
||||||
|
buildinfo.display(),
|
||||||
|
changes.display()
|
||||||
|
);
|
||||||
|
artifacts.push(buildinfo);
|
||||||
|
artifacts.push(changes);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("failed to generate .buildinfo/.changes: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect the binary artifacts (.deb/.udeb) registered by the build in
|
||||||
|
/// `debian/files`, returning their paths inside the build context
|
||||||
|
/// (`<build_root>/<filename>`). `debian/files` is the canonical record of
|
||||||
|
/// which binary packages `debian/rules binary` produced, so we retrieve
|
||||||
|
/// exactly those instead of globbing the build root (which would also pick
|
||||||
|
/// up stale files copied alongside the package tree).
|
||||||
|
fn collect_binary_artifacts(
|
||||||
|
ctx: &Arc<Context>,
|
||||||
|
package_dir: &str,
|
||||||
|
build_root: &str,
|
||||||
|
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||||
|
let files_content = ctx
|
||||||
|
.read_file(&Path::new(package_dir).join("debian/files"))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let files_list = crate::debian::FilesList::parse(&files_content)
|
||||||
|
.map_err(|e| format!("invalid debian/files in {package_dir}: {e}"))?;
|
||||||
|
let upload_dir = Path::new(build_root);
|
||||||
|
let mut artifacts = Vec::new();
|
||||||
|
for entry in files_list.iter() {
|
||||||
|
if matches!(entry.package_type.as_deref(), Some("deb") | Some("udeb")) {
|
||||||
|
artifacts.push(upload_dir.join(&entry.filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(artifacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate `.buildinfo` and `.changes` for the finished binary build,
|
/// Generate `.buildinfo` and `.changes` for the finished binary build,
|
||||||
@@ -349,9 +399,7 @@ fn generate_upload_metadata(
|
|||||||
cross: bool,
|
cross: bool,
|
||||||
env: &HashMap<String, String>,
|
env: &HashMap<String, String>,
|
||||||
ctx: &Arc<Context>,
|
ctx: &Arc<Context>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(PathBuf, PathBuf), Box<dyn Error>> {
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
let changelog_path = Path::new(package_dir).join("debian/changelog");
|
let changelog_path = Path::new(package_dir).join("debian/changelog");
|
||||||
let changelog_content = ctx.read_file(&changelog_path)?;
|
let changelog_content = ctx.read_file(&changelog_path)?;
|
||||||
let entry = crate::debian::parse_changelog_entry_from_str(&changelog_content)?;
|
let entry = crate::debian::parse_changelog_entry_from_str(&changelog_content)?;
|
||||||
@@ -409,12 +457,7 @@ fn generate_upload_metadata(
|
|||||||
Path::new(build_root),
|
Path::new(build_root),
|
||||||
&opts,
|
&opts,
|
||||||
)?;
|
)?;
|
||||||
log::info!(
|
Ok((buildinfo, changes))
|
||||||
"generated upload metadata: {} and {}",
|
|
||||||
buildinfo.display(),
|
|
||||||
changes.display()
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply quilt patches before building, if the package provides a
|
/// Apply quilt patches before building, if the package provides a
|
||||||
|
|||||||
+24
-23
@@ -17,10 +17,13 @@ pub enum BuildMode {
|
|||||||
|
|
||||||
/// Build package in 'cwd' to a .deb
|
/// Build package in 'cwd' to a .deb
|
||||||
///
|
///
|
||||||
/// Returns the list of produced .deb files retrieved locally. When `ui` is
|
/// Returns the list of produced artifacts (.deb files plus the upload
|
||||||
|
/// metadata `.buildinfo`/`.changes`) retrieved locally, identified from
|
||||||
|
/// `debian/files` and the native metadata generation rather than by
|
||||||
|
/// globbing the build root (which would surface stale files). When `ui` is
|
||||||
/// set, a live view (status bar + rolling log pane) is displayed and all
|
/// set, a live view (status bar + rolling log pane) is displayed and all
|
||||||
/// subprocess output is captured through it; on failure the widget is cleared
|
/// subprocess output is captured through it; on failure the widget is
|
||||||
/// and a summary of captured errors is printed.
|
/// cleared and a summary of captured errors is printed.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn build_binary_package(
|
pub async fn build_binary_package(
|
||||||
arch: Option<&str>,
|
arch: Option<&str>,
|
||||||
@@ -33,6 +36,7 @@ pub async fn build_binary_package(
|
|||||||
inject_packages: Option<&[&str]>,
|
inject_packages: Option<&[&str]>,
|
||||||
ctx: Option<Arc<Context>>,
|
ctx: Option<Arc<Context>>,
|
||||||
ui: Option<Arc<DebUi>>,
|
ui: Option<Arc<DebUi>>,
|
||||||
|
jobs: Option<usize>,
|
||||||
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||||
let result = build_binary_package_impl(
|
let result = build_binary_package_impl(
|
||||||
arch,
|
arch,
|
||||||
@@ -45,6 +49,7 @@ pub async fn build_binary_package(
|
|||||||
inject_packages,
|
inject_packages,
|
||||||
ctx,
|
ctx,
|
||||||
&ui,
|
&ui,
|
||||||
|
jobs,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -68,6 +73,7 @@ async fn build_binary_package_impl(
|
|||||||
inject_packages: Option<&[&str]>,
|
inject_packages: Option<&[&str]>,
|
||||||
ctx: Option<Arc<Context>>,
|
ctx: Option<Arc<Context>>,
|
||||||
ui: &Option<Arc<DebUi>>,
|
ui: &Option<Arc<DebUi>>,
|
||||||
|
jobs: Option<usize>,
|
||||||
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||||
let cwd = cwd.unwrap_or_else(|| Path::new("."));
|
let cwd = cwd.unwrap_or_else(|| Path::new("."));
|
||||||
|
|
||||||
@@ -145,8 +151,11 @@ async fn build_binary_package_impl(
|
|||||||
.ok_or("Cannot find parent directory name")?;
|
.ok_or("Cannot find parent directory name")?;
|
||||||
let build_root = format!("{}/{}", build_root, parent_dir_name.to_str().unwrap());
|
let build_root = format!("{}/{}", build_root, parent_dir_name.to_str().unwrap());
|
||||||
|
|
||||||
// Run the build using target build mode
|
// Run the build using target build mode. It returns the exact set of
|
||||||
match mode {
|
// artifacts produced by this build (binary packages registered in
|
||||||
|
// debian/files plus the generated .buildinfo/.changes), as paths
|
||||||
|
// inside the build context.
|
||||||
|
let remote_files: Vec<PathBuf> = match mode {
|
||||||
BuildMode::Local => {
|
BuildMode::Local => {
|
||||||
local::build(
|
local::build(
|
||||||
&package,
|
&package,
|
||||||
@@ -160,33 +169,24 @@ async fn build_binary_package_impl(
|
|||||||
inject_packages,
|
inject_packages,
|
||||||
build_ctx.clone(),
|
build_ctx.clone(),
|
||||||
ui.clone(),
|
ui.clone(),
|
||||||
|
jobs,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Retrieve produced artifacts (.deb files plus the upload metadata
|
// Retrieve the produced artifacts (binary packages plus the upload
|
||||||
// (.buildinfo/.changes) generated natively after the build)
|
// metadata) to the parent directory.
|
||||||
if let Some(u) = ui {
|
if let Some(u) = ui {
|
||||||
u.phase(Phase::RetrievingArtifacts);
|
u.phase(Phase::RetrievingArtifacts);
|
||||||
}
|
}
|
||||||
let remote_files = build_ctx.list_files(Path::new(&build_root))?;
|
let total_debs = remote_files.len();
|
||||||
let deb_files: Vec<PathBuf> = remote_files
|
|
||||||
.into_iter()
|
|
||||||
.filter(|f| {
|
|
||||||
f.extension().is_some_and(|ext| {
|
|
||||||
matches!(
|
|
||||||
ext.to_str(),
|
|
||||||
Some("deb") | Some("buildinfo") | Some("changes")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let total_debs = deb_files.len();
|
|
||||||
|
|
||||||
let mut artifacts = Vec::with_capacity(total_debs);
|
let mut artifacts = Vec::with_capacity(total_debs);
|
||||||
for (idx, remote_file) in deb_files.iter().enumerate() {
|
for (idx, remote_file) in remote_files.iter().enumerate() {
|
||||||
let file_name = remote_file.file_name().ok_or("Invalid remote filename")?;
|
let file_name = remote_file
|
||||||
|
.file_name()
|
||||||
|
.ok_or("Invalid remote filename")?;
|
||||||
let local_dest = parent_dir.join(file_name);
|
let local_dest = parent_dir.join(file_name);
|
||||||
build_ctx.retrieve_path(remote_file, &local_dest)?;
|
build_ctx.retrieve_path(remote_file, &local_dest)?;
|
||||||
artifacts.push(local_dest);
|
artifacts.push(local_dest);
|
||||||
@@ -414,6 +414,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Some(ctx),
|
Some(ctx),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Cannot build binary package (deb)");
|
.expect("Cannot build binary package (deb)");
|
||||||
|
|||||||
+10
@@ -78,6 +78,7 @@ fn main() {
|
|||||||
.long_help("Cross-compile for target architecture (instead of using qemu-binfmt)\nNote that most packages cannot be cross-compiled").required(false))
|
.long_help("Cross-compile for target architecture (instead of using qemu-binfmt)\nNote that most packages cannot be cross-compiled").required(false))
|
||||||
.arg(arg!(--mode <mode> "Change build mode [local]").required(false)
|
.arg(arg!(--mode <mode> "Change build mode [local]").required(false)
|
||||||
.long_help("Change build mode [local]\nDefault will chose depending on other parameters, don't provide if unsure"))
|
.long_help("Change build mode [local]\nDefault will chose depending on other parameters, don't provide if unsure"))
|
||||||
|
.arg(arg!(-j --jobs <jobs> "Number of parallel build jobs (default: number of CPUs available in the build context)").required(false))
|
||||||
.arg(arg!(--verbose "Show raw tool output instead of the live build view").required(false)
|
.arg(arg!(--verbose "Show raw tool output instead of the live build view").required(false)
|
||||||
.long_help("Show raw tool output instead of the live build view.\nAlso implied by RUST_LOG=debug for pkh's own logs.")),
|
.long_help("Show raw tool output instead of the live build view.\nAlso implied by RUST_LOG=debug for pkh's own logs.")),
|
||||||
)
|
)
|
||||||
@@ -313,6 +314,14 @@ fn main() {
|
|||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let jobs = sub_matches.get_one::<String>("jobs").map(|s| s.as_str());
|
||||||
|
let jobs = jobs.map(|j| {
|
||||||
|
j.parse::<usize>().unwrap_or_else(|_| {
|
||||||
|
error!("Invalid --jobs value '{}': expected a positive integer", j);
|
||||||
|
std::process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
// Live build view: disabled by --verbose or when stdout is not a
|
// Live build view: disabled by --verbose or when stdout is not a
|
||||||
// terminal (DebUi handles the non-TTY case itself)
|
// terminal (DebUi handles the non-TTY case itself)
|
||||||
let ui = if verbose {
|
let ui = if verbose {
|
||||||
@@ -333,6 +342,7 @@ fn main() {
|
|||||||
inject_packages,
|
inject_packages,
|
||||||
None,
|
None,
|
||||||
ui.clone(),
|
ui.clone(),
|
||||||
|
jobs,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user