deb: drive build_binary_package through the BuildView port
The binary build joins the source build on the reporting ports: build_binary_package takes a DebBuildOptions struct (replacing eleven positional arguments), reports target, phases, progress and the outcome through the environment-agnostic BuildView, and the Phase enum with its default classifiers moves from the terminal widget into the deb module (announced through the enter_phase helper). DebUi loses its inherent event methods and only implements the port; tee logging and the SIGINT behavior are unchanged. No behavior change for the CLI; headless consumers pass report::Quiet.
This commit is contained in:
+7
-2
@@ -24,7 +24,7 @@ use crate::debian::{
|
|||||||
ChecksumEntry, ControlInfo, FileChecksums, FilesEntry, FilesList, parse_paragraphs,
|
ChecksumEntry, ControlInfo, FileChecksums, FilesEntry, FilesList, parse_paragraphs,
|
||||||
};
|
};
|
||||||
use crate::logfmt::{DpkgSourceClassifier, GenericClassifier};
|
use crate::logfmt::{DpkgSourceClassifier, GenericClassifier};
|
||||||
use crate::report::{BuildView, Prompter};
|
use crate::report::{BuildTarget, BuildView, Prompter};
|
||||||
|
|
||||||
/// Whether the upload distributes the upstream orig tarballs (`--orig`),
|
/// Whether the upload distributes the upstream orig tarballs (`--orig`),
|
||||||
/// mirroring the `dpkg-genchanges` source styles.
|
/// mirroring the `dpkg-genchanges` source styles.
|
||||||
@@ -294,7 +294,12 @@ pub fn run_source_build(
|
|||||||
|
|
||||||
let ctrl = ControlInfo::parse(&control_path)?;
|
let ctrl = ControlInfo::parse(&control_path)?;
|
||||||
|
|
||||||
view.target(&entry.source, &entry.version.full(), &entry.distribution);
|
view.target(BuildTarget {
|
||||||
|
package: &entry.source,
|
||||||
|
version: &entry.version.full(),
|
||||||
|
target: &entry.distribution,
|
||||||
|
source_only: true,
|
||||||
|
});
|
||||||
|
|
||||||
let source_display = entry.source.clone();
|
let source_display = entry.source.clone();
|
||||||
|
|
||||||
|
|||||||
+17
-26
@@ -1,5 +1,6 @@
|
|||||||
use crate::context::{self, Context, ContextConfig};
|
use crate::context::{self, Context, ContextConfig};
|
||||||
use crate::ui::deb::{DebUi, Phase};
|
use crate::deb::{Phase, enter_phase};
|
||||||
|
use crate::report::BuildView;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -312,7 +313,7 @@ impl EphemeralContextGuard {
|
|||||||
series: &str,
|
series: &str,
|
||||||
arch: Option<&str>,
|
arch: Option<&str>,
|
||||||
base_ctx: Arc<Context>,
|
base_ctx: Arc<Context>,
|
||||||
ui: Option<Arc<DebUi>>,
|
view: &dyn BuildView,
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
// Save the globally-installed context so Drop can restore exactly
|
// Save the globally-installed context so Drop can restore exactly
|
||||||
// this handle: concurrent builds install their own ephemeral
|
// this handle: concurrent builds install their own ephemeral
|
||||||
@@ -355,7 +356,7 @@ impl EphemeralContextGuard {
|
|||||||
|
|
||||||
// Download and extract the chroot tarball
|
// Download and extract the chroot tarball
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
Self::download_and_extract_chroot(series, arch, &chroot_path, base_ctx.clone(), &ui)
|
Self::download_and_extract_chroot(series, arch, &chroot_path, base_ctx.clone(), view)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// The guard (and its Drop) never materializes on this path, so
|
// The guard (and its Drop) never materializes on this path, so
|
||||||
@@ -408,7 +409,7 @@ impl EphemeralContextGuard {
|
|||||||
arch: Option<&str>,
|
arch: Option<&str>,
|
||||||
chroot_path: &PathBuf,
|
chroot_path: &PathBuf,
|
||||||
ctx: Arc<context::Context>,
|
ctx: Arc<context::Context>,
|
||||||
ui: &Option<Arc<DebUi>>,
|
view: &dyn BuildView,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
// Clone ctx for use in create_device_nodes after download_chroot_tarball consumes it
|
// Clone ctx for use in create_device_nodes after download_chroot_tarball consumes it
|
||||||
let ctx_for_devices = ctx.clone();
|
let ctx_for_devices = ctx.clone();
|
||||||
@@ -464,10 +465,8 @@ impl EphemeralContextGuard {
|
|||||||
series,
|
series,
|
||||||
arch
|
arch
|
||||||
);
|
);
|
||||||
if let Some(u) = ui {
|
enter_phase(view, Phase::PreparingChroot);
|
||||||
u.phase(Phase::PreparingChroot);
|
Self::download_chroot_tarball(series, arch, &tarball_path, ctx, view).await?;
|
||||||
}
|
|
||||||
Self::download_chroot_tarball(series, arch, &tarball_path, ctx, ui).await?;
|
|
||||||
} else {
|
} else {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Using cached chroot tarball for {} (arch: {:?})",
|
"Using cached chroot tarball for {} (arch: {:?})",
|
||||||
@@ -478,16 +477,12 @@ impl EphemeralContextGuard {
|
|||||||
|
|
||||||
// Extract tarball to chroot directory
|
// Extract tarball to chroot directory
|
||||||
log::debug!("Extracting chroot tarball to {}...", chroot_path.display());
|
log::debug!("Extracting chroot tarball to {}...", chroot_path.display());
|
||||||
if let Some(u) = ui {
|
enter_phase(view, Phase::ExtractingChroot);
|
||||||
u.phase(Phase::ExtractingChroot);
|
Self::extract_tarball(&tarball_path, chroot_path, view)?;
|
||||||
}
|
|
||||||
Self::extract_tarball(&tarball_path, chroot_path, ui.as_deref())?;
|
|
||||||
|
|
||||||
// Create device nodes in the chroot
|
// Create device nodes in the chroot
|
||||||
log::debug!("Creating device nodes in chroot...");
|
log::debug!("Creating device nodes in chroot...");
|
||||||
if let Some(u) = ui {
|
enter_phase(view, Phase::FinalizingChroot);
|
||||||
u.phase(Phase::FinalizingChroot);
|
|
||||||
}
|
|
||||||
Self::create_device_nodes(chroot_path, ctx_for_devices.clone())?;
|
Self::create_device_nodes(chroot_path, ctx_for_devices.clone())?;
|
||||||
|
|
||||||
// Bind mount /proc from host into chroot (before entering unshare namespace)
|
// Bind mount /proc from host into chroot (before entering unshare namespace)
|
||||||
@@ -503,7 +498,7 @@ impl EphemeralContextGuard {
|
|||||||
arch: Option<&str>,
|
arch: Option<&str>,
|
||||||
tarball_path: &Path,
|
tarball_path: &Path,
|
||||||
ctx: Arc<context::Context>,
|
ctx: Arc<context::Context>,
|
||||||
ui: &Option<Arc<DebUi>>,
|
view: &dyn BuildView,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
// Create a lock file to make sure that noone tries to use the file while it's not fully downloaded
|
// Create a lock file to make sure that noone tries to use the file while it's not fully downloaded
|
||||||
let lockfile_path = tarball_path.with_extension("lock");
|
let lockfile_path = tarball_path.with_extension("lock");
|
||||||
@@ -537,8 +532,8 @@ impl EphemeralContextGuard {
|
|||||||
cmd.arg(series)
|
cmd.arg(series)
|
||||||
.arg(tarball_path.to_string_lossy().to_string());
|
.arg(tarball_path.to_string_lossy().to_string());
|
||||||
|
|
||||||
if let Some(u) = ui {
|
if let Some(s) = view.sink() {
|
||||||
cmd.capture(u.sink());
|
cmd.capture(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = cmd.status()?;
|
let status = cmd.status()?;
|
||||||
@@ -575,7 +570,7 @@ impl EphemeralContextGuard {
|
|||||||
fn extract_tarball(
|
fn extract_tarball(
|
||||||
tarball_path: &PathBuf,
|
tarball_path: &PathBuf,
|
||||||
chroot_path: &PathBuf,
|
chroot_path: &PathBuf,
|
||||||
ui: Option<&DebUi>,
|
view: &dyn BuildView,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
// Create the chroot directory
|
// Create the chroot directory
|
||||||
fs::create_dir_all(chroot_path)?;
|
fs::create_dir_all(chroot_path)?;
|
||||||
@@ -593,15 +588,11 @@ impl EphemeralContextGuard {
|
|||||||
let mut entry = entry?;
|
let mut entry = entry?;
|
||||||
entry.unpack_in(chroot_path)?;
|
entry.unpack_in(chroot_path)?;
|
||||||
count += 1;
|
count += 1;
|
||||||
if count.is_multiple_of(100)
|
if count.is_multiple_of(100) {
|
||||||
&& let Some(u) = ui
|
view.message(&format!("Extracting chroot… ({count} files)"));
|
||||||
{
|
|
||||||
u.progress_message(&format!("Extracting chroot… ({count} files)"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(u) = ui {
|
view.message(&format!("Extracting chroot… ({count} files)"));
|
||||||
u.progress_message(&format!("Extracting chroot… ({count} files)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-88
@@ -1,9 +1,9 @@
|
|||||||
/// Local binary package building
|
/// Local binary package building
|
||||||
/// Directly calling 'debian/rules' in current context
|
/// Directly calling 'debian/rules' in current context
|
||||||
use crate::context::{Context, ContextCommand, LineSink};
|
use crate::context::{Context, ContextCommand, LineSink};
|
||||||
use crate::deb::find_dsc_file;
|
use crate::deb::{Phase, enter_phase, find_dsc_file};
|
||||||
use crate::logfmt::QuiltClassifier;
|
use crate::logfmt::QuiltClassifier;
|
||||||
use crate::ui::deb::{DebUi, Phase};
|
use crate::report::BuildView;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -33,13 +33,13 @@ pub async fn build(
|
|||||||
pocket: Option<&str>,
|
pocket: Option<&str>,
|
||||||
build_root: &str,
|
build_root: &str,
|
||||||
cross: bool,
|
cross: bool,
|
||||||
ppa: Option<&[&str]>,
|
ppa: &[String],
|
||||||
inject_packages: Option<&[&str]>,
|
inject_packages: &[String],
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
ui: Option<Arc<DebUi>>,
|
view: &dyn BuildView,
|
||||||
jobs: Option<usize>,
|
jobs: Option<usize>,
|
||||||
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
) -> 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>> = view.sink();
|
||||||
|
|
||||||
// Environment
|
// Environment
|
||||||
let mut env = HashMap::<String, String>::new();
|
let mut env = HashMap::<String, String>::new();
|
||||||
@@ -83,55 +83,53 @@ pub async fn build(
|
|||||||
let mut added_ppas: Vec<(&str, &str)> = Vec::new();
|
let mut added_ppas: Vec<(&str, &str)> = Vec::new();
|
||||||
|
|
||||||
// Add PPA repositories if specified
|
// Add PPA repositories if specified
|
||||||
if let Some(ppas) = ppa {
|
for ppa_str in ppa {
|
||||||
for ppa_str in ppas {
|
// PPA format: user/ppa_name
|
||||||
// PPA format: user/ppa_name
|
let parts: Vec<&str> = ppa_str.split('/').collect();
|
||||||
let parts: Vec<&str> = ppa_str.split('/').collect();
|
if parts.len() == 2 {
|
||||||
if parts.len() == 2 {
|
let base_url = crate::package_info::ppa_to_base_url(parts[0], parts[1]);
|
||||||
let base_url = crate::package_info::ppa_to_base_url(parts[0], parts[1]);
|
|
||||||
|
|
||||||
// Add new PPA source if not found
|
// Add new PPA source if not found
|
||||||
if !sources.iter().any(|s| s.uri.contains(&base_url)) {
|
if !sources.iter().any(|s| s.uri.contains(&base_url)) {
|
||||||
// Get host and target architectures
|
// Get host and target architectures
|
||||||
let host_arch = crate::get_current_arch();
|
let host_arch = crate::get_current_arch();
|
||||||
let target_arch = arch;
|
let target_arch = arch;
|
||||||
|
|
||||||
// Create architectures list with both host and target if different
|
// Create architectures list with both host and target if different
|
||||||
let mut architectures = vec![host_arch.clone()];
|
let mut architectures = vec![host_arch.clone()];
|
||||||
if host_arch != *target_arch {
|
if host_arch != *target_arch {
|
||||||
architectures.push(target_arch.to_string());
|
architectures.push(target_arch.to_string());
|
||||||
}
|
|
||||||
|
|
||||||
// Create suite list with all Ubuntu series
|
|
||||||
let suites = vec![series.to_string()];
|
|
||||||
|
|
||||||
let new_source = crate::apt::sources::SourceEntry {
|
|
||||||
enabled: true,
|
|
||||||
kind: crate::apt::sources::SourceKind::Deb,
|
|
||||||
components: vec!["main".to_string()],
|
|
||||||
architectures: architectures.clone(),
|
|
||||||
signed_by: None,
|
|
||||||
trusted: None,
|
|
||||||
suite: suites,
|
|
||||||
uri: base_url,
|
|
||||||
// No origin: saved to the pkh-owned added-sources file
|
|
||||||
origin: None,
|
|
||||||
};
|
|
||||||
sources.push(new_source);
|
|
||||||
modified = true;
|
|
||||||
added_ppas.push((parts[0], parts[1]));
|
|
||||||
log::info!(
|
|
||||||
"Added PPA: {} for series {} with architectures {:?}",
|
|
||||||
ppa_str,
|
|
||||||
series,
|
|
||||||
architectures
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return Err(
|
// Create suite list with all Ubuntu series
|
||||||
format!("Invalid PPA format: '{}'. Expected: user/ppa_name", ppa_str).into(),
|
let suites = vec![series.to_string()];
|
||||||
|
|
||||||
|
let new_source = crate::apt::sources::SourceEntry {
|
||||||
|
enabled: true,
|
||||||
|
kind: crate::apt::sources::SourceKind::Deb,
|
||||||
|
components: vec!["main".to_string()],
|
||||||
|
architectures: architectures.clone(),
|
||||||
|
signed_by: None,
|
||||||
|
trusted: None,
|
||||||
|
suite: suites,
|
||||||
|
uri: base_url,
|
||||||
|
// No origin: saved to the pkh-owned added-sources file
|
||||||
|
origin: None,
|
||||||
|
};
|
||||||
|
sources.push(new_source);
|
||||||
|
modified = true;
|
||||||
|
added_ppas.push((parts[0], parts[1]));
|
||||||
|
log::info!(
|
||||||
|
"Added PPA: {} for series {} with architectures {:?}",
|
||||||
|
ppa_str,
|
||||||
|
series,
|
||||||
|
architectures
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
format!("Invalid PPA format: '{}'. Expected: user/ppa_name", ppa_str).into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +191,7 @@ pub async fn build(
|
|||||||
|
|
||||||
// Update package lists
|
// Update package lists
|
||||||
log::debug!("Updating package lists for local build...");
|
log::debug!("Updating package lists for local build...");
|
||||||
if let Some(u) = &ui {
|
enter_phase(view, Phase::UpdatingPackageLists);
|
||||||
u.phase(Phase::UpdatingPackageLists);
|
|
||||||
}
|
|
||||||
let status = cap(
|
let status = cap(
|
||||||
ctx.command("apt-get").envs(env.clone()).arg("update"),
|
ctx.command("apt-get").envs(env.clone()).arg("update"),
|
||||||
&sink,
|
&sink,
|
||||||
@@ -234,9 +230,7 @@ pub async fn build(
|
|||||||
cmd.arg(format!("libc6:{arch}"));
|
cmd.arg(format!("libc6:{arch}"));
|
||||||
cmd.arg(format!("libc6-dev:{arch}"));
|
cmd.arg(format!("libc6-dev:{arch}"));
|
||||||
}
|
}
|
||||||
if let Some(u) = &ui {
|
enter_phase(view, Phase::InstallingEssentials);
|
||||||
u.phase(Phase::InstallingEssentials);
|
|
||||||
}
|
|
||||||
let status = cap(&mut cmd, &sink).status()?;
|
let status = cap(&mut cmd, &sink).status()?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err("Could not install essential packages for the build".into());
|
return Err("Could not install essential packages for the build".into());
|
||||||
@@ -261,18 +255,16 @@ pub async fn build(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply quilt patches if the package provides a patch series
|
// Apply quilt patches if the package provides a patch series
|
||||||
apply_quilt_patches(package_dir_str, &env, ctx.clone(), &ui, &sink)?;
|
apply_quilt_patches(package_dir_str, &env, ctx.clone(), view, &sink)?;
|
||||||
|
|
||||||
// Install injected packages if specified
|
// Install injected packages if specified
|
||||||
if let Some(packages) = inject_packages {
|
if !inject_packages.is_empty() {
|
||||||
install_injected_packages(packages, &env, ctx.clone(), &ui, &sink)?;
|
install_injected_packages(inject_packages, &env, ctx.clone(), view, &sink)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install arch-specific build dependencies
|
// Install arch-specific build dependencies
|
||||||
log::debug!("Installing arch-specific build dependencies...");
|
log::debug!("Installing arch-specific build dependencies...");
|
||||||
if let Some(u) = &ui {
|
enter_phase(view, Phase::InstallingBuildDeps);
|
||||||
u.phase(Phase::InstallingBuildDeps);
|
|
||||||
}
|
|
||||||
let mut cmd = ctx.command("apt-get");
|
let mut cmd = ctx.command("apt-get");
|
||||||
cmd.current_dir(package_dir_str)
|
cmd.current_dir(package_dir_str)
|
||||||
.envs(env.clone())
|
.envs(env.clone())
|
||||||
@@ -286,9 +278,7 @@ pub async fn build(
|
|||||||
|
|
||||||
// If build-dep fails, we try to explain the failure using dose-debcheck
|
// If build-dep fails, we try to explain the failure using dose-debcheck
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
if let Some(u) = &ui {
|
view.suspend();
|
||||||
u.suspend();
|
|
||||||
}
|
|
||||||
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
|
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
|
||||||
return Err("Could not install build-dependencies for the build".into());
|
return Err("Could not install build-dependencies for the build".into());
|
||||||
}
|
}
|
||||||
@@ -320,9 +310,7 @@ pub async fn build(
|
|||||||
|
|
||||||
// If build-dep fails, we try to explain the failure using dose-debcheck
|
// If build-dep fails, we try to explain the failure using dose-debcheck
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
if let Some(u) = &ui {
|
view.suspend();
|
||||||
u.suspend();
|
|
||||||
}
|
|
||||||
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
|
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
|
||||||
return Err("Could not install build-dependencies for the build".into());
|
return Err("Could not install build-dependencies for the build".into());
|
||||||
}
|
}
|
||||||
@@ -330,9 +318,7 @@ pub async fn build(
|
|||||||
|
|
||||||
// Run the build step
|
// Run the build step
|
||||||
log::debug!("Building (debian/rules build) package...");
|
log::debug!("Building (debian/rules build) package...");
|
||||||
if let Some(u) = &ui {
|
enter_phase(view, Phase::Building);
|
||||||
u.phase(Phase::Building);
|
|
||||||
}
|
|
||||||
let status = cap(
|
let status = cap(
|
||||||
ctx.command("debian/rules")
|
ctx.command("debian/rules")
|
||||||
.current_dir(package_dir_str)
|
.current_dir(package_dir_str)
|
||||||
@@ -346,9 +332,7 @@ pub async fn build(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the 'binary' step to produce deb
|
// Run the 'binary' step to produce deb
|
||||||
if let Some(u) = &ui {
|
enter_phase(view, Phase::ProducingBinaries);
|
||||||
u.phase(Phase::ProducingBinaries);
|
|
||||||
}
|
|
||||||
let status = cap(
|
let status = cap(
|
||||||
ctx.command("fakeroot")
|
ctx.command("fakeroot")
|
||||||
.current_dir(package_dir_str)
|
.current_dir(package_dir_str)
|
||||||
@@ -495,7 +479,7 @@ fn apply_quilt_patches(
|
|||||||
package_dir: &str,
|
package_dir: &str,
|
||||||
env: &HashMap<String, String>,
|
env: &HashMap<String, String>,
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
ui: &Option<Arc<DebUi>>,
|
view: &dyn BuildView,
|
||||||
sink: &Option<Arc<dyn LineSink>>,
|
sink: &Option<Arc<dyn LineSink>>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let series_path = Path::new(package_dir).join("debian/patches/series");
|
let series_path = Path::new(package_dir).join("debian/patches/series");
|
||||||
@@ -555,12 +539,10 @@ fn apply_quilt_patches(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply all patches listed in the series
|
// Apply all patches listed in the series
|
||||||
if let Some(u) = ui {
|
view.phase(
|
||||||
u.phase_with(
|
Phase::ApplyingPatches.label(),
|
||||||
Phase::ApplyingPatches,
|
Box::new(QuiltClassifier::new(total_patches)),
|
||||||
Box::new(QuiltClassifier::new(total_patches)),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut patch_env = env.clone();
|
let mut patch_env = env.clone();
|
||||||
patch_env.insert("QUILT_PATCHES".to_string(), "debian/patches".to_string());
|
patch_env.insert("QUILT_PATCHES".to_string(), "debian/patches".to_string());
|
||||||
let status = cap(
|
let status = cap(
|
||||||
@@ -631,17 +613,15 @@ fn pin_pocket(pocket_suite: &str, ctx: &Arc<Context>) -> Result<(), Box<dyn Erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn install_injected_packages(
|
fn install_injected_packages(
|
||||||
packages: &[&str],
|
packages: &[String],
|
||||||
env: &HashMap<String, String>,
|
env: &HashMap<String, String>,
|
||||||
ctx: Arc<Context>,
|
ctx: Arc<Context>,
|
||||||
ui: &Option<Arc<DebUi>>,
|
view: &dyn BuildView,
|
||||||
sink: &Option<Arc<dyn LineSink>>,
|
sink: &Option<Arc<dyn LineSink>>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
log::info!("Installing injected packages: {:?}", packages);
|
log::info!("Installing injected packages: {:?}", packages);
|
||||||
|
|
||||||
if let Some(u) = ui {
|
enter_phase(view, Phase::InjectingPackages);
|
||||||
u.phase(Phase::InjectingPackages);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate .deb files from package names
|
// Separate .deb files from package names
|
||||||
let mut deb_files: Vec<String> = Vec::new();
|
let mut deb_files: Vec<String> = Vec::new();
|
||||||
@@ -661,7 +641,7 @@ fn install_injected_packages(
|
|||||||
);
|
);
|
||||||
deb_files.push(chroot_path.to_string_lossy().to_string());
|
deb_files.push(chroot_path.to_string_lossy().to_string());
|
||||||
} else {
|
} else {
|
||||||
package_names.push(pkg);
|
package_names.push(pkg.as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,6 +835,13 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
apply_quilt_patches(tree.to_str().unwrap(), &HashMap::new(), ctx, &None, &None).unwrap();
|
apply_quilt_patches(
|
||||||
|
tree.to_str().unwrap(),
|
||||||
|
&HashMap::new(),
|
||||||
|
ctx,
|
||||||
|
&crate::report::Quiet,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+183
-102
@@ -5,7 +5,11 @@ pub(crate) mod ephemeral;
|
|||||||
mod local;
|
mod local;
|
||||||
|
|
||||||
use crate::context::{self, Context};
|
use crate::context::{self, Context};
|
||||||
use crate::ui::deb::{DebUi, Phase};
|
use crate::logfmt::{
|
||||||
|
AptInstallClassifier, AptUpdateClassifier, Classifier, GenericClassifier, MakeClassifier,
|
||||||
|
MmdebstrapClassifier, QuiltClassifier,
|
||||||
|
};
|
||||||
|
use crate::report::{BuildTarget, BuildView};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -17,67 +21,163 @@ pub enum BuildMode {
|
|||||||
Local,
|
Local,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Phases of a binary build, announced to the [`BuildView`]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Phase {
|
||||||
|
/// Downloading the chroot tarball (mmdebstrap)
|
||||||
|
PreparingChroot,
|
||||||
|
/// Extracting the chroot tarball
|
||||||
|
ExtractingChroot,
|
||||||
|
/// Device nodes, /proc bind mount, etc.
|
||||||
|
FinalizingChroot,
|
||||||
|
/// apt-get update
|
||||||
|
UpdatingPackageLists,
|
||||||
|
/// Installing build-essential & co
|
||||||
|
InstallingEssentials,
|
||||||
|
/// quilt push -a
|
||||||
|
ApplyingPatches,
|
||||||
|
/// --inject packages
|
||||||
|
InjectingPackages,
|
||||||
|
/// apt-get build-dep
|
||||||
|
InstallingBuildDeps,
|
||||||
|
/// debian/rules build
|
||||||
|
Building,
|
||||||
|
/// fakeroot debian/rules binary
|
||||||
|
ProducingBinaries,
|
||||||
|
/// Retrieving produced .deb files
|
||||||
|
RetrievingArtifacts,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Phase {
|
||||||
|
/// Human-readable label displayed in the status bar
|
||||||
|
pub fn label(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Phase::PreparingChroot => "Preparing chroot",
|
||||||
|
Phase::ExtractingChroot => "Extracting chroot",
|
||||||
|
Phase::FinalizingChroot => "Finalizing chroot",
|
||||||
|
Phase::UpdatingPackageLists => "Updating package lists",
|
||||||
|
Phase::InstallingEssentials => "Installing essential packages",
|
||||||
|
Phase::ApplyingPatches => "Applying patches",
|
||||||
|
Phase::InjectingPackages => "Injecting packages",
|
||||||
|
Phase::InstallingBuildDeps => "Installing build dependencies",
|
||||||
|
Phase::Building => "Building package",
|
||||||
|
Phase::ProducingBinaries => "Producing binary packages",
|
||||||
|
Phase::RetrievingArtifacts => "Retrieving artifacts",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default line classifier rewriting a phase's subprocess output
|
||||||
|
fn default_classifier(phase: Phase) -> Box<dyn Classifier> {
|
||||||
|
match phase {
|
||||||
|
Phase::PreparingChroot => Box::new(MmdebstrapClassifier::new()),
|
||||||
|
Phase::ExtractingChroot | Phase::FinalizingChroot => Box::new(GenericClassifier::new()),
|
||||||
|
Phase::UpdatingPackageLists => Box::new(AptUpdateClassifier::new()),
|
||||||
|
Phase::InstallingEssentials => Box::new(AptInstallClassifier::new("Installing essentials")),
|
||||||
|
Phase::ApplyingPatches => Box::new(QuiltClassifier::new(0)),
|
||||||
|
Phase::InjectingPackages => Box::new(AptInstallClassifier::new("Injecting packages")),
|
||||||
|
Phase::InstallingBuildDeps => {
|
||||||
|
Box::new(AptInstallClassifier::new("Installing build dependencies"))
|
||||||
|
}
|
||||||
|
Phase::Building | Phase::ProducingBinaries => Box::new(MakeClassifier::new()),
|
||||||
|
Phase::RetrievingArtifacts => Box::new(GenericClassifier::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enter `phase` on the view with its default line classifier
|
||||||
|
pub(crate) fn enter_phase(view: &dyn BuildView, phase: Phase) {
|
||||||
|
view.phase(phase.label(), default_classifier(phase));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters of one [`build_binary_package`] call.
|
||||||
|
pub struct DebBuildOptions<'a> {
|
||||||
|
/// Target architecture; defaults to the host architecture.
|
||||||
|
pub arch: Option<String>,
|
||||||
|
/// Target distribution series; defaults to the changelog series
|
||||||
|
/// (UNRELEASED resolves to the vendor's development series).
|
||||||
|
pub series: Option<String>,
|
||||||
|
/// Distribution pocket to resolve build-dependencies from.
|
||||||
|
pub pocket: Option<String>,
|
||||||
|
/// Source tree to build; defaults to the process working directory.
|
||||||
|
pub cwd: Option<PathBuf>,
|
||||||
|
/// Cross-compile for the target architecture instead of using
|
||||||
|
/// qemu-binfmt.
|
||||||
|
pub cross: bool,
|
||||||
|
/// Build mode; defaults to [`BuildMode::Local`].
|
||||||
|
pub mode: Option<BuildMode>,
|
||||||
|
/// PPAs to add for build-dependencies (`user/ppa_name`).
|
||||||
|
pub ppa: Vec<String>,
|
||||||
|
/// Packages to inject into the build environment before build-dep
|
||||||
|
/// (.deb paths, archive names or PPA packages).
|
||||||
|
pub inject: Vec<String>,
|
||||||
|
/// Parallel build jobs; defaults to the core count available in the
|
||||||
|
/// build context.
|
||||||
|
pub jobs: Option<usize>,
|
||||||
|
/// Explicit build context; defaults to the current context.
|
||||||
|
pub ctx: Option<Arc<Context>>,
|
||||||
|
/// Where build events (phases, progress, outcome) are reported.
|
||||||
|
pub view: &'a dyn BuildView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DebBuildOptions<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
static QUIET: crate::report::Quiet = crate::report::Quiet;
|
||||||
|
DebBuildOptions {
|
||||||
|
arch: None,
|
||||||
|
series: None,
|
||||||
|
pocket: None,
|
||||||
|
cwd: None,
|
||||||
|
cross: false,
|
||||||
|
mode: None,
|
||||||
|
ppa: Vec::new(),
|
||||||
|
inject: Vec::new(),
|
||||||
|
jobs: None,
|
||||||
|
ctx: None,
|
||||||
|
view: &QUIET,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build package in 'cwd' to a .deb
|
/// Build package in 'cwd' to a .deb
|
||||||
///
|
///
|
||||||
/// Returns the list of produced artifacts (.deb files plus the upload
|
/// Returns the list of produced artifacts (.deb files plus the upload
|
||||||
/// metadata `.buildinfo`/`.changes`) retrieved locally, identified from
|
/// metadata `.buildinfo`/`.changes`) retrieved locally, identified from
|
||||||
/// `debian/files` and the native metadata generation rather than by
|
/// `debian/files` and the native metadata generation rather than by
|
||||||
/// globbing the build root (which would surface stale files). When `ui` is
|
/// globbing the build root (which would surface stale files). Subprocess
|
||||||
/// set, a live view (status bar + rolling log pane) is displayed and all
|
/// output is captured through the view's sink (live view + tee log for the
|
||||||
/// subprocess output is captured through it; on failure the widget is
|
/// terminal adapter); on failure the view is cleared and prints a summary
|
||||||
/// cleared and a summary of captured errors is printed.
|
/// of captured errors.
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub async fn build_binary_package(
|
pub async fn build_binary_package(
|
||||||
arch: Option<&str>,
|
opts: DebBuildOptions<'_>,
|
||||||
series: Option<&str>,
|
|
||||||
pocket: Option<&str>,
|
|
||||||
cwd: Option<&Path>,
|
|
||||||
cross: bool,
|
|
||||||
mode: Option<BuildMode>,
|
|
||||||
ppa: Option<&[&str]>,
|
|
||||||
inject_packages: Option<&[&str]>,
|
|
||||||
ctx: Option<Arc<Context>>,
|
|
||||||
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 view = opts.view;
|
||||||
arch,
|
let result = build_binary_package_impl(opts).await;
|
||||||
series,
|
|
||||||
pocket,
|
|
||||||
cwd,
|
|
||||||
cross,
|
|
||||||
mode,
|
|
||||||
ppa,
|
|
||||||
inject_packages,
|
|
||||||
ctx,
|
|
||||||
&ui,
|
|
||||||
jobs,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let (Some(u), Err(_)) = (&ui, &result) {
|
if result.is_err() {
|
||||||
u.finish_failure();
|
view.finish_failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of [`build_binary_package`], without failure handling
|
/// Implementation of [`build_binary_package`], without failure handling
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
async fn build_binary_package_impl(
|
async fn build_binary_package_impl(
|
||||||
arch: Option<&str>,
|
opts: DebBuildOptions<'_>,
|
||||||
series: Option<&str>,
|
|
||||||
pocket: Option<&str>,
|
|
||||||
cwd: Option<&Path>,
|
|
||||||
cross: bool,
|
|
||||||
mode: Option<BuildMode>,
|
|
||||||
ppa: Option<&[&str]>,
|
|
||||||
inject_packages: Option<&[&str]>,
|
|
||||||
ctx: Option<Arc<Context>>,
|
|
||||||
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 DebBuildOptions {
|
||||||
|
ref arch,
|
||||||
|
ref series,
|
||||||
|
ref pocket,
|
||||||
|
ref cwd,
|
||||||
|
cross,
|
||||||
|
ref mode,
|
||||||
|
ref ppa,
|
||||||
|
ref inject,
|
||||||
|
ref jobs,
|
||||||
|
ref ctx,
|
||||||
|
view,
|
||||||
|
} = opts;
|
||||||
|
let cwd = cwd.as_deref().unwrap_or_else(|| Path::new("."));
|
||||||
|
|
||||||
// Parse changelog to get package name, version and series
|
// Parse changelog to get package name, version and series
|
||||||
let changelog_path = cwd.join("debian/changelog");
|
let changelog_path = cwd.join("debian/changelog");
|
||||||
@@ -101,44 +201,43 @@ async fn build_binary_package_impl(
|
|||||||
&package_series
|
&package_series
|
||||||
};
|
};
|
||||||
let current_arch = crate::get_current_arch();
|
let current_arch = crate::get_current_arch();
|
||||||
let arch = arch.unwrap_or(¤t_arch);
|
let arch = arch.as_deref().unwrap_or(¤t_arch);
|
||||||
|
|
||||||
// Make sure we select a specific mode, either using user-requested
|
// Make sure we select a specific mode, either using user-requested
|
||||||
// or by using default for user-supplied parameters
|
// or by using default for user-supplied parameters
|
||||||
let mode = if let Some(m) = mode {
|
let default_mode = BuildMode::Local;
|
||||||
m
|
let mode = mode.as_ref().unwrap_or(&default_mode);
|
||||||
} else {
|
|
||||||
// By default, we use local build
|
|
||||||
BuildMode::Local
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create an ephemeral unshare context for all Local builds
|
// Create an ephemeral unshare context for all Local builds
|
||||||
// Use qemu_binfmt when target architecture differs from host and cross is not requested
|
// Use qemu_binfmt when target architecture differs from host and cross is not requested
|
||||||
let chroot_arch = if mode == BuildMode::Local && arch != current_arch && !cross {
|
let chroot_arch = if mode == &BuildMode::Local && arch != current_arch && !cross {
|
||||||
Some(arch)
|
Some(arch)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use provided context or get current
|
// Use provided context or get current
|
||||||
let base_ctx = ctx.unwrap_or_else(context::current);
|
let base_ctx = ctx.clone().unwrap_or_else(context::current);
|
||||||
|
|
||||||
// Identify the target in the live UI once the changelog is parsed, so
|
// Identify the target in the live view once the changelog is parsed, so
|
||||||
// even the chroot download output is attributed and tee'd
|
// even the chroot download output is attributed and tee'd
|
||||||
if let Some(u) = ui {
|
view.target(BuildTarget {
|
||||||
u.set_target(&package, &version, series, arch);
|
package: &package,
|
||||||
}
|
version: &version,
|
||||||
|
target: &format!("{series}/{arch}"),
|
||||||
|
source_only: false,
|
||||||
|
});
|
||||||
|
|
||||||
// Create an ephemeral unshare context for all Local builds. It is kept in
|
// Create an ephemeral unshare context for all Local builds. It is kept in
|
||||||
// this scope so it outlives the guarded section below and is only dropped
|
// this scope so it outlives the guarded section below and is only dropped
|
||||||
// once the live view has been cleared.
|
// once the live view has been cleared.
|
||||||
let mut guard = if mode == BuildMode::Local {
|
let mut guard = if *mode == BuildMode::Local {
|
||||||
Some(
|
Some(
|
||||||
ephemeral::EphemeralContextGuard::new_with_context(
|
ephemeral::EphemeralContextGuard::new_with_context(
|
||||||
series,
|
series,
|
||||||
chroot_arch,
|
chroot_arch,
|
||||||
base_ctx.clone(),
|
base_ctx.clone(),
|
||||||
ui.clone(),
|
view,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
@@ -179,14 +278,14 @@ async fn build_binary_package_impl(
|
|||||||
&version,
|
&version,
|
||||||
arch,
|
arch,
|
||||||
series,
|
series,
|
||||||
pocket,
|
pocket.as_deref(),
|
||||||
&build_root,
|
&build_root,
|
||||||
cross,
|
cross,
|
||||||
ppa,
|
ppa,
|
||||||
inject_packages,
|
inject,
|
||||||
build_ctx.clone(),
|
build_ctx.clone(),
|
||||||
ui.clone(),
|
view,
|
||||||
jobs,
|
*jobs,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
@@ -194,9 +293,7 @@ async fn build_binary_package_impl(
|
|||||||
|
|
||||||
// Retrieve the produced artifacts (binary packages plus the upload
|
// Retrieve the produced artifacts (binary packages plus the upload
|
||||||
// metadata) to the parent directory.
|
// metadata) to the parent directory.
|
||||||
if let Some(u) = ui {
|
enter_phase(view, Phase::RetrievingArtifacts);
|
||||||
u.phase(Phase::RetrievingArtifacts);
|
|
||||||
}
|
|
||||||
let total_debs = remote_files.len();
|
let total_debs = remote_files.len();
|
||||||
|
|
||||||
let mut artifacts = Vec::with_capacity(total_debs);
|
let mut artifacts = Vec::with_capacity(total_debs);
|
||||||
@@ -206,14 +303,10 @@ async fn build_binary_package_impl(
|
|||||||
build_ctx.retrieve_path(remote_file, &local_dest)?;
|
build_ctx.retrieve_path(remote_file, &local_dest)?;
|
||||||
artifacts.push(local_dest);
|
artifacts.push(local_dest);
|
||||||
|
|
||||||
if let Some(u) = ui {
|
view.progress("Retrieving artifacts", idx + 1, total_debs);
|
||||||
u.count_progress("Retrieving artifacts", idx + 1, total_debs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(u) = ui {
|
view.finish_success(&artifacts);
|
||||||
u.finish_success(&artifacts, u.elapsed());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(artifacts)
|
Ok(artifacts)
|
||||||
}
|
}
|
||||||
@@ -222,9 +315,7 @@ async fn build_binary_package_impl(
|
|||||||
// Clear the live view before returning: the ephemeral guard is dropped at
|
// Clear the live view before returning: the ephemeral guard is dropped at
|
||||||
// the end of this function and its cleanup commands (umount, rm -rf of
|
// the end of this function and its cleanup commands (umount, rm -rf of
|
||||||
// the chroot) inherit the terminal, so they must not fight the widget.
|
// the chroot) inherit the terminal, so they must not fight the widget.
|
||||||
if let Some(u) = ui {
|
view.suspend();
|
||||||
u.suspend();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark build as successful to trigger chroot cleanup
|
// Mark build as successful to trigger chroot cleanup
|
||||||
if result.is_ok()
|
if result.is_ok()
|
||||||
@@ -446,19 +537,14 @@ mod tests {
|
|||||||
log::debug!("Package directory: {}", cwd.display());
|
log::debug!("Package directory: {}", cwd.display());
|
||||||
|
|
||||||
log::info!("Starting binary package build...");
|
log::info!("Starting binary package build...");
|
||||||
crate::deb::build_binary_package(
|
crate::deb::build_binary_package(DebBuildOptions {
|
||||||
arch,
|
arch: arch.map(str::to_string),
|
||||||
Some(series),
|
series: Some(series.to_string()),
|
||||||
None,
|
|
||||||
Some(&cwd),
|
|
||||||
cross,
|
cross,
|
||||||
None,
|
cwd: Some(cwd.to_path_buf()),
|
||||||
None,
|
ctx: Some(ctx),
|
||||||
None,
|
..Default::default()
|
||||||
Some(ctx),
|
})
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.expect("Cannot build binary package (deb)");
|
.expect("Cannot build binary package (deb)");
|
||||||
log::info!("Successfully built binary package");
|
log::info!("Successfully built binary package");
|
||||||
@@ -620,19 +706,14 @@ mod tests {
|
|||||||
|
|
||||||
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap());
|
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local).unwrap());
|
||||||
|
|
||||||
crate::deb::build_binary_package(
|
crate::deb::build_binary_package(DebBuildOptions {
|
||||||
Some("arm64"),
|
arch: Some("arm64".to_string()),
|
||||||
Some("noble"),
|
series: Some("noble".to_string()),
|
||||||
None,
|
cwd: Some(pkg_dir),
|
||||||
Some(&pkg_dir),
|
cross: true,
|
||||||
true,
|
ctx: Some(ctx),
|
||||||
None,
|
..Default::default()
|
||||||
None,
|
})
|
||||||
None,
|
|
||||||
Some(ctx),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.expect("Cannot cross-build package declaring Build-Depends-Indep");
|
.expect("Cannot cross-build package declaring Build-Depends-Indep");
|
||||||
|
|
||||||
|
|||||||
+24
-36
@@ -573,28 +573,21 @@ fn main() {
|
|||||||
}
|
}
|
||||||
Some(("deb", sub_matches)) => {
|
Some(("deb", sub_matches)) => {
|
||||||
let cwd = current_dir_or_exit();
|
let cwd = current_dir_or_exit();
|
||||||
let series = sub_matches.get_one::<String>("series").map(|s| s.as_str());
|
let series = sub_matches.get_one::<String>("series").cloned();
|
||||||
let pocket = sub_matches.get_one::<String>("pocket").map(|s| s.as_str());
|
let pocket = sub_matches.get_one::<String>("pocket").cloned();
|
||||||
let arch = sub_matches.get_one::<String>("arch").map(|s| s.as_str());
|
let arch = sub_matches.get_one::<String>("arch").cloned();
|
||||||
let cross = sub_matches.get_one::<bool>("cross").unwrap_or(&false);
|
let cross = sub_matches
|
||||||
let ppa: Vec<&str> = sub_matches
|
.get_one::<bool>("cross")
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(false);
|
||||||
|
let ppa: Vec<String> = sub_matches
|
||||||
.get_many::<String>("ppa")
|
.get_many::<String>("ppa")
|
||||||
.map(|v| v.map(|s| s.as_str()).collect())
|
.map(|v| v.cloned().collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let ppa = if ppa.is_empty() {
|
let inject: Vec<String> = sub_matches
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ppa.as_slice())
|
|
||||||
};
|
|
||||||
let inject_packages: Vec<&str> = sub_matches
|
|
||||||
.get_many::<String>("inject")
|
.get_many::<String>("inject")
|
||||||
.map(|v| v.map(|s| s.as_str()).collect())
|
.map(|v| v.cloned().collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let inject_packages = if inject_packages.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(inject_packages.as_slice())
|
|
||||||
};
|
|
||||||
let mode: Option<&str> = sub_matches.get_one::<String>("mode").map(|s| s.as_str());
|
let mode: Option<&str> = sub_matches.get_one::<String>("mode").map(|s| s.as_str());
|
||||||
let mode: Option<pkh::deb::BuildMode> = match mode {
|
let mode: Option<pkh::deb::BuildMode> = match mode {
|
||||||
Some("local") => Some(pkh::deb::BuildMode::Local),
|
Some("local") => Some(pkh::deb::BuildMode::Local),
|
||||||
@@ -613,36 +606,31 @@ fn main() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// Live build view: disabled by --verbose or when stdout is not a
|
// Live build view, unless --verbose (DebUi additionally disables
|
||||||
// terminal (DebUi handles the non-TTY case itself)
|
// itself when stdout is not a terminal)
|
||||||
let ui = if verbose {
|
let quiet = pkh::report::Quiet;
|
||||||
None
|
let live = pkh::ui::deb::DebUi::new(&multi);
|
||||||
} else {
|
let view: &dyn pkh::report::BuildView = if verbose { &quiet } else { &live };
|
||||||
Some(std::sync::Arc::new(pkh::ui::deb::DebUi::new(&multi)))
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
let result = rt.block_on(async {
|
||||||
pkh::deb::build_binary_package(
|
pkh::deb::build_binary_package(pkh::deb::DebBuildOptions {
|
||||||
arch,
|
arch,
|
||||||
series,
|
series,
|
||||||
pocket,
|
pocket,
|
||||||
Some(cwd.as_path()),
|
cwd: Some(cwd.clone()),
|
||||||
*cross,
|
cross,
|
||||||
mode,
|
mode,
|
||||||
ppa,
|
ppa,
|
||||||
inject_packages,
|
inject,
|
||||||
None,
|
|
||||||
ui.clone(),
|
|
||||||
jobs,
|
jobs,
|
||||||
)
|
view,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(artifacts) => {
|
Ok(_) => info!("Done."),
|
||||||
let _ = artifacts;
|
|
||||||
info!("Done.");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{}", e);
|
error!("{}", e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|||||||
+7
-14
@@ -624,20 +624,13 @@ pub async fn offer_verification(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ui = Some(std::sync::Arc::new(crate::ui::deb::DebUi::new(multi)));
|
let view = crate::ui::deb::DebUi::new(multi);
|
||||||
if let Err(e) = crate::deb::build_binary_package(
|
if let Err(e) = crate::deb::build_binary_package(crate::deb::DebBuildOptions {
|
||||||
None,
|
series: Some(opts.series.clone()),
|
||||||
Some(&opts.series),
|
cwd: Some(tree.clone()),
|
||||||
None,
|
view: &view,
|
||||||
Some(&tree),
|
..Default::default()
|
||||||
false,
|
})
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
ui,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
log::error!("Verification binary build failed: {e}");
|
log::error!("Verification binary build failed: {e}");
|
||||||
|
|||||||
+29
-6
@@ -21,16 +21,33 @@ use std::sync::Arc;
|
|||||||
use crate::context::LineSink;
|
use crate::context::LineSink;
|
||||||
use crate::logfmt::Classifier;
|
use crate::logfmt::Classifier;
|
||||||
|
|
||||||
|
/// Identity of the build whose events follow, as announced through
|
||||||
|
/// [`BuildView::target`].
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BuildTarget<'a> {
|
||||||
|
/// Source package name (e.g. `hello`).
|
||||||
|
pub package: &'a str,
|
||||||
|
/// Full version being built (e.g. `2.10-3`).
|
||||||
|
pub version: &'a str,
|
||||||
|
/// What the build targets: a distribution series, optionally with an
|
||||||
|
/// architecture (`noble`, `sid`, `noble/arm64`).
|
||||||
|
pub target: &'a str,
|
||||||
|
/// Whether this is a source-only build (producing a `.dsc`) as opposed
|
||||||
|
/// to a binary build (producing `.deb` files).
|
||||||
|
pub source_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Observer of a running build: target identification, phases, status
|
/// Observer of a running build: target identification, phases, status
|
||||||
/// messages, progress and the final outcome.
|
/// messages, progress and the final outcome.
|
||||||
///
|
///
|
||||||
/// Implement this to observe [`crate::build`] flows from any frontend. All
|
/// Implement this to observe the [`crate::build`] and [`crate::deb`] flows
|
||||||
/// events arrive in order from the build thread; long-lived views are
|
/// from any frontend. All events arrive in order from the build thread;
|
||||||
/// expected to be `Send + Sync` because builds may run inside async tasks.
|
/// long-lived views are expected to be `Send + Sync` because builds may run
|
||||||
|
/// inside async tasks.
|
||||||
pub trait BuildView: Send + Sync {
|
pub trait BuildView: Send + Sync {
|
||||||
/// The build target was identified: `package` at `version`, built for
|
/// The build target was identified; the events that follow belong to it
|
||||||
/// `target` (a distribution series, optionally with an architecture).
|
/// (including the earliest subprocess output, e.g. a chroot download).
|
||||||
fn target(&self, _package: &str, _version: &str, _target: &str) {}
|
fn target(&self, _target: BuildTarget) {}
|
||||||
|
|
||||||
/// A named phase started (e.g. "Applying patches"). `classifier`
|
/// A named phase started (e.g. "Applying patches"). `classifier`
|
||||||
/// rewrites the phase's raw subprocess lines (see [`crate::logfmt`])
|
/// rewrites the phase's raw subprocess lines (see [`crate::logfmt`])
|
||||||
@@ -63,6 +80,12 @@ pub trait BuildView: Send + Sync {
|
|||||||
/// diagnostics it collected through [`BuildView::sink`].
|
/// diagnostics it collected through [`BuildView::sink`].
|
||||||
fn finish_failure(&self) {}
|
fn finish_failure(&self) {}
|
||||||
|
|
||||||
|
/// Release the display before writing directly to the shared terminal
|
||||||
|
/// (passthrough diagnostics, cleanup commands that inherit it). The
|
||||||
|
/// release is final for this build; later events may be dropped. A
|
||||||
|
/// no-op for views without a display.
|
||||||
|
fn suspend(&self) {}
|
||||||
|
|
||||||
/// Whether this view presents build results to the user by itself;
|
/// Whether this view presents build results to the user by itself;
|
||||||
/// callers use this to fall back to plain-line rendering when it does
|
/// callers use this to fall back to plain-line rendering when it does
|
||||||
/// not (headless views, verbose mode).
|
/// not (headless views, verbose mode).
|
||||||
|
|||||||
+48
-154
@@ -21,10 +21,8 @@ use directories::ProjectDirs;
|
|||||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
use crate::context::{LineSink, Stream};
|
use crate::context::{LineSink, Stream};
|
||||||
use crate::logfmt::{
|
use crate::logfmt::{Action, Classifier, GenericClassifier};
|
||||||
Action, AptInstallClassifier, AptUpdateClassifier, Classifier, GenericClassifier,
|
use crate::report::BuildTarget;
|
||||||
MakeClassifier, MmdebstrapClassifier, QuiltClassifier,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Number of lines displayed in the rolling pane
|
/// Number of lines displayed in the rolling pane
|
||||||
const PANE_LINES: usize = 10;
|
const PANE_LINES: usize = 10;
|
||||||
@@ -32,69 +30,6 @@ const PANE_LINES: usize = 10;
|
|||||||
/// Minimum interval between pane redraws
|
/// Minimum interval between pane redraws
|
||||||
const REDRAW_INTERVAL: Duration = Duration::from_millis(50);
|
const REDRAW_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
/// Build phases of `pkh deb`, shown in the status bar
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Phase {
|
|
||||||
/// Downloading the chroot tarball (mmdebstrap)
|
|
||||||
PreparingChroot,
|
|
||||||
/// Extracting the chroot tarball
|
|
||||||
ExtractingChroot,
|
|
||||||
/// Device nodes, /proc bind mount, etc.
|
|
||||||
FinalizingChroot,
|
|
||||||
/// apt-get update
|
|
||||||
UpdatingPackageLists,
|
|
||||||
/// Installing build-essential & co
|
|
||||||
InstallingEssentials,
|
|
||||||
/// quilt push -a
|
|
||||||
ApplyingPatches,
|
|
||||||
/// --inject packages
|
|
||||||
InjectingPackages,
|
|
||||||
/// apt-get build-dep
|
|
||||||
InstallingBuildDeps,
|
|
||||||
/// debian/rules build
|
|
||||||
Building,
|
|
||||||
/// fakeroot debian/rules binary
|
|
||||||
ProducingBinaries,
|
|
||||||
/// Retrieving produced .deb files
|
|
||||||
RetrievingArtifacts,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Phase {
|
|
||||||
/// Human-readable label displayed in the status bar
|
|
||||||
pub fn label(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Phase::PreparingChroot => "Preparing chroot",
|
|
||||||
Phase::ExtractingChroot => "Extracting chroot",
|
|
||||||
Phase::FinalizingChroot => "Finalizing chroot",
|
|
||||||
Phase::UpdatingPackageLists => "Updating package lists",
|
|
||||||
Phase::InstallingEssentials => "Installing essential packages",
|
|
||||||
Phase::ApplyingPatches => "Applying patches",
|
|
||||||
Phase::InjectingPackages => "Injecting packages",
|
|
||||||
Phase::InstallingBuildDeps => "Installing build dependencies",
|
|
||||||
Phase::Building => "Building package",
|
|
||||||
Phase::ProducingBinaries => "Producing binary packages",
|
|
||||||
Phase::RetrievingArtifacts => "Retrieving artifacts",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default classifier used for a given phase
|
|
||||||
fn default_classifier(phase: Phase) -> Box<dyn Classifier> {
|
|
||||||
match phase {
|
|
||||||
Phase::PreparingChroot => Box::new(MmdebstrapClassifier::new()),
|
|
||||||
Phase::ExtractingChroot | Phase::FinalizingChroot => Box::new(GenericClassifier::new()),
|
|
||||||
Phase::UpdatingPackageLists => Box::new(AptUpdateClassifier::new()),
|
|
||||||
Phase::InstallingEssentials => Box::new(AptInstallClassifier::new("Installing essentials")),
|
|
||||||
Phase::ApplyingPatches => Box::new(QuiltClassifier::new(0)),
|
|
||||||
Phase::InjectingPackages => Box::new(AptInstallClassifier::new("Injecting packages")),
|
|
||||||
Phase::InstallingBuildDeps => {
|
|
||||||
Box::new(AptInstallClassifier::new("Installing build dependencies"))
|
|
||||||
}
|
|
||||||
Phase::Building | Phase::ProducingBinaries => Box::new(MakeClassifier::new()),
|
|
||||||
Phase::RetrievingArtifacts => Box::new(GenericClassifier::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Visual kind of a pane line, driving its color
|
/// Visual kind of a pane line, driving its color
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
enum Kind {
|
enum Kind {
|
||||||
@@ -129,11 +64,9 @@ struct Shared {
|
|||||||
|
|
||||||
/// Live build view for `pkh deb` / `pkh build`
|
/// Live build view for `pkh deb` / `pkh build`
|
||||||
///
|
///
|
||||||
/// Create one per build (disabled automatically when stdout is not a TTY or
|
/// Create one per build (it disables itself automatically when stdout is not
|
||||||
/// when the user requests verbose output), then either pass it down through
|
/// a TTY) and pass it down through the [`crate::report::BuildView`] port.
|
||||||
/// the [`crate::report::BuildView`] port (source builds) or as
|
/// Subprocess output reaches it through [`crate::report::BuildView::sink`].
|
||||||
/// `Option<Arc<DebUi>>` (the `pkh deb` flow, until it migrates), and feed
|
|
||||||
/// subprocess output through [`DebUi::sink`].
|
|
||||||
pub struct DebUi {
|
pub struct DebUi {
|
||||||
shared: Arc<Shared>,
|
shared: Arc<Shared>,
|
||||||
}
|
}
|
||||||
@@ -252,20 +185,7 @@ impl DebUi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch to a phase, installing its default classifier
|
/// Switch to an arbitrary status label with a custom classifier
|
||||||
pub fn phase(&self, phase: Phase) {
|
|
||||||
self.phase_custom(phase.label(), default_classifier(phase));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Switch to a phase with a custom classifier (e.g. quilt with a known
|
|
||||||
/// patch count)
|
|
||||||
pub fn phase_with(&self, phase: Phase, classifier: Box<dyn Classifier>) {
|
|
||||||
self.phase_custom(phase.label(), classifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Switch to an arbitrary status label with a custom classifier; the
|
|
||||||
/// shared implementation behind [`DebUi::phase`], [`DebUi::phase_with`]
|
|
||||||
/// and the [`BuildView`] port
|
|
||||||
fn phase_custom(&self, label: &str, classifier: Box<dyn Classifier>) {
|
fn phase_custom(&self, label: &str, classifier: Box<dyn Classifier>) {
|
||||||
{
|
{
|
||||||
let mut st = self.shared.state.lock().unwrap();
|
let mut st = self.shared.state.lock().unwrap();
|
||||||
@@ -281,47 +201,18 @@ impl DebUi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the status bar message directly (for in-process work such as
|
|
||||||
/// tarball extraction that has no subprocess output)
|
|
||||||
pub fn progress_message(&self, msg: &str) {
|
|
||||||
if self.active() {
|
|
||||||
self.shared.top.set_message(msg.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drive the determinate progress bar directly (e.g. artifact retrieval)
|
|
||||||
pub fn count_progress(&self, label: &str, pos: usize, total: usize) {
|
|
||||||
if !self.active() || total == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
apply_progress(
|
|
||||||
&self.shared.top,
|
|
||||||
&mut self.shared.state.lock().unwrap(),
|
|
||||||
pos as u64,
|
|
||||||
total as u64,
|
|
||||||
);
|
|
||||||
self.shared.top.set_message(label.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the widget is enabled and still drawn
|
/// Whether the widget is enabled and still drawn
|
||||||
fn active(&self) -> bool {
|
fn active(&self) -> bool {
|
||||||
self.shared.enabled && !self.shared.suspended.load(Ordering::SeqCst)
|
self.shared.enabled && !self.shared.suspended.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a sink feeding this view; pass it to `ContextCommand::capture`
|
/// Release the widget from the terminal (e.g. before printing
|
||||||
pub fn sink(self: &Arc<Self>) -> Arc<dyn LineSink> {
|
/// passthrough diagnostics or letting child cleanup commands write to
|
||||||
Arc::new(Sink {
|
/// the terminal); idempotent
|
||||||
shared: self.shared.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the widget from the terminal (e.g. before printing passthrough
|
|
||||||
/// diagnostics or letting child cleanup commands write to the terminal);
|
|
||||||
/// idempotent
|
|
||||||
///
|
///
|
||||||
/// Steady ticks are disabled first: otherwise a tick can redraw a frame
|
/// Steady ticks are disabled first: otherwise a tick can redraw a frame
|
||||||
/// right after the clear, leaving stale copies of the widget on screen.
|
/// right after the clear, leaving stale copies of the widget on screen.
|
||||||
pub fn suspend(&self) {
|
fn suspend(&self) {
|
||||||
if !self.shared.enabled {
|
if !self.shared.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -334,20 +225,8 @@ impl DebUi {
|
|||||||
self.shared.pane.finish_and_clear();
|
self.shared.pane.finish_and_clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the widget and print a success summary with the artifacts,
|
/// Success outcome body: clear the widget and print the artifacts,
|
||||||
/// rendered relative to the working directory when possible
|
/// rendered relative to the working directory when possible
|
||||||
pub fn finish_success(&self, artifacts: &[PathBuf], elapsed: Duration) {
|
|
||||||
self.success_summary(artifacts, elapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear the widget and print a failure summary (recent captured errors
|
|
||||||
/// and the path to the full log)
|
|
||||||
pub fn finish_failure(&self) {
|
|
||||||
self.failure_summary();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shared body of the success outcome ([`DebUi::finish_success`] and the
|
|
||||||
/// [`BuildView`] port)
|
|
||||||
fn success_summary(&self, artifacts: &[PathBuf], elapsed: Duration) {
|
fn success_summary(&self, artifacts: &[PathBuf], elapsed: Duration) {
|
||||||
self.suspend();
|
self.suspend();
|
||||||
if self.shared.enabled && !artifacts.is_empty() {
|
if self.shared.enabled && !artifacts.is_empty() {
|
||||||
@@ -358,8 +237,8 @@ impl DebUi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared body of the failure outcome ([`DebUi::finish_failure`] and the
|
/// Failure outcome body: clear the widget and print a summary (recent
|
||||||
/// [`BuildView`] port)
|
/// captured errors and the path to the full log)
|
||||||
fn failure_summary(&self) {
|
fn failure_summary(&self) {
|
||||||
self.suspend();
|
self.suspend();
|
||||||
|
|
||||||
@@ -383,31 +262,31 @@ impl DebUi {
|
|||||||
eprintln!("Full log: {}", log_path.display());
|
eprintln!("Full log: {}", log_path.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Time elapsed since the view was created
|
|
||||||
pub fn elapsed(&self) -> Duration {
|
|
||||||
self.shared.started.elapsed()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Path of the full build log file
|
|
||||||
pub fn log_path(&self) -> PathBuf {
|
|
||||||
self.shared.log_path.lock().unwrap().clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`crate::report::BuildView`] port: forwards build events to the live
|
/// [`crate::report::BuildView`] port: forwards build events to the live
|
||||||
/// widget, so core flows can drive the view without knowing it is a
|
/// widget, so core flows drive the view without knowing it is a terminal
|
||||||
/// terminal widget. Where the inherent methods keep their richer signatures
|
/// widget.
|
||||||
/// (e.g. [`DebUi::finish_success`] takes the elapsed duration explicitly for
|
|
||||||
/// the `pkh deb` flow), the port bridges them.
|
|
||||||
impl crate::report::BuildView for DebUi {
|
impl crate::report::BuildView for DebUi {
|
||||||
fn target(&self, package: &str, version: &str, target: &str) {
|
fn target(&self, target: BuildTarget<'_>) {
|
||||||
|
let kind = if target.source_only {
|
||||||
|
"source package "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
if self.shared.enabled {
|
if self.shared.enabled {
|
||||||
self.shared.top.set_prefix(format!(
|
self.shared.top.set_prefix(format!(
|
||||||
"Building source package {package} ({version}) for {target}"
|
"Building {kind}{} ({}) for {}",
|
||||||
|
target.package, target.version, target.target
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
self.open_log("build", package, version, &format!("for {target}"));
|
let log_kind = if target.source_only { "build" } else { "deb" };
|
||||||
|
self.open_log(
|
||||||
|
log_kind,
|
||||||
|
target.package,
|
||||||
|
target.version,
|
||||||
|
&format!("for {}", target.target),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phase(&self, name: &str, classifier: Box<dyn Classifier>) {
|
fn phase(&self, name: &str, classifier: Box<dyn Classifier>) {
|
||||||
@@ -415,11 +294,22 @@ impl crate::report::BuildView for DebUi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn message(&self, text: &str) {
|
fn message(&self, text: &str) {
|
||||||
self.progress_message(text);
|
if self.active() {
|
||||||
|
self.shared.top.set_message(text.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn progress(&self, label: &str, pos: usize, total: usize) {
|
fn progress(&self, label: &str, pos: usize, total: usize) {
|
||||||
self.count_progress(label, pos, total);
|
if !self.active() || total == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
apply_progress(
|
||||||
|
&self.shared.top,
|
||||||
|
&mut self.shared.state.lock().unwrap(),
|
||||||
|
pos as u64,
|
||||||
|
total as u64,
|
||||||
|
);
|
||||||
|
self.shared.top.set_message(label.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sink(&self) -> Option<Arc<dyn LineSink>> {
|
fn sink(&self) -> Option<Arc<dyn LineSink>> {
|
||||||
@@ -429,13 +319,17 @@ impl crate::report::BuildView for DebUi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finish_success(&self, artifacts: &[PathBuf]) {
|
fn finish_success(&self, artifacts: &[PathBuf]) {
|
||||||
self.success_summary(artifacts, self.elapsed());
|
self.success_summary(artifacts, self.shared.started.elapsed());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_failure(&self) {
|
fn finish_failure(&self) {
|
||||||
self.failure_summary();
|
self.failure_summary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn suspend(&self) {
|
||||||
|
DebUi::suspend(self);
|
||||||
|
}
|
||||||
|
|
||||||
fn is_enabled(&self) -> bool {
|
fn is_enabled(&self) -> bool {
|
||||||
self.shared.enabled
|
self.shared.enabled
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user