deb: cross-compilation, ephemeral contexts, local builds
All checks were successful
CI / build (push) Successful in 7m18s
All checks were successful
CI / build (push) Successful in 7m18s
Multiple changes: - New contexts (schroot, unshare) - Cross-building quirks, with ephemeral contexts and repositories management - Contexts with parents, global context manager, better lifetime handling - Local building of binary packages - Pull: pulling dsc files by default - Many small bugfixes and changes Co-authored-by: Valentin Haudiquet <valentin.haudiquet@canonical.com> Co-committed-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
This commit was merged in pull request #1.
This commit is contained in:
178
src/deb/mod.rs
Normal file
178
src/deb/mod.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
mod cross;
|
||||
mod local;
|
||||
mod sbuild;
|
||||
|
||||
use crate::context;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum BuildMode {
|
||||
Sbuild,
|
||||
Local,
|
||||
}
|
||||
|
||||
pub fn build_binary_package(
|
||||
arch: Option<&str>,
|
||||
series: Option<&str>,
|
||||
cwd: Option<&Path>,
|
||||
cross: bool,
|
||||
mode: Option<BuildMode>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let cwd = cwd.unwrap_or_else(|| Path::new("."));
|
||||
|
||||
// Parse changelog to get package name, version and series
|
||||
let changelog_path = cwd.join("debian/changelog");
|
||||
let (package, version, package_series) =
|
||||
crate::changelog::parse_changelog_header(&changelog_path)?;
|
||||
let series = if let Some(s) = series {
|
||||
s
|
||||
} else {
|
||||
&package_series
|
||||
};
|
||||
let current_arch = crate::get_current_arch();
|
||||
let arch = arch.unwrap_or(¤t_arch);
|
||||
|
||||
// Make sure we select a specific mode, either using user-requested
|
||||
// or by using default for user-supplied parameters
|
||||
let mode = if let Some(m) = mode {
|
||||
m
|
||||
} else {
|
||||
// For cross-compilation, we use local with an ephemeral context
|
||||
// created by the cross-compilation handler (see below)
|
||||
if cross {
|
||||
BuildMode::Local
|
||||
} else {
|
||||
// By default, we use sbuild
|
||||
BuildMode::Sbuild
|
||||
}
|
||||
};
|
||||
|
||||
// Specific case: native cross-compilation, we don't allow that
|
||||
// instead this wraps to an automatic unshare chroot
|
||||
// using an ephemeral context
|
||||
let _guard = if cross && mode == BuildMode::Local {
|
||||
Some(cross::EphemeralContextGuard::new(series)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Prepare build directory
|
||||
let ctx = context::current();
|
||||
let build_root = ctx.create_temp_dir()?;
|
||||
|
||||
// Ensure availability of all needed files for the build
|
||||
let parent_dir = cwd.parent().ok_or("Cannot find parent directory")?;
|
||||
ctx.ensure_available(parent_dir, &build_root)?;
|
||||
let parent_dir_name = parent_dir
|
||||
.file_name()
|
||||
.ok_or("Cannot find parent directory name")?;
|
||||
let build_root = format!("{}/{}", build_root, parent_dir_name.to_str().unwrap());
|
||||
|
||||
// Run the build using target build mode
|
||||
match mode {
|
||||
BuildMode::Local => local::build(&package, &version, arch, series, &build_root, cross)?,
|
||||
BuildMode::Sbuild => sbuild::build(&package, &version, arch, series, &build_root, cross)?,
|
||||
};
|
||||
|
||||
// Retrieve produced .deb files
|
||||
let remote_files = ctx.list_files(Path::new(&build_root))?;
|
||||
for remote_file in remote_files {
|
||||
if remote_file.extension().is_some_and(|ext| ext == "deb") {
|
||||
let file_name = remote_file.file_name().ok_or("Invalid remote filename")?;
|
||||
let local_dest = parent_dir.join(file_name);
|
||||
ctx.retrieve_path(&remote_file, &local_dest)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_dsc_file(
|
||||
build_root: &str,
|
||||
package: &str,
|
||||
version: &str,
|
||||
) -> Result<PathBuf, Box<dyn Error>> {
|
||||
// Strip epoch from version (e.g., "1:2.3.4-5" -> "2.3.4-5")
|
||||
let version_without_epoch = version.split_once(':').map(|(_, v)| v).unwrap_or(version);
|
||||
let dsc_name = format!("{}_{}.dsc", package, version_without_epoch);
|
||||
let dsc_path = PathBuf::from(build_root).join(&dsc_name);
|
||||
|
||||
if !dsc_path.exists() {
|
||||
return Err(format!("Could not find .dsc file at {}", dsc_path.display()).into());
|
||||
}
|
||||
Ok(dsc_path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
async fn test_build_end_to_end(package: &str, series: &str, arch: Option<&str>, cross: bool) {
|
||||
log::info!(
|
||||
"Starting end-to-end test for package: {} (series: {}, arch: {:?}, cross: {})",
|
||||
package,
|
||||
series,
|
||||
arch,
|
||||
cross
|
||||
);
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let cwd = temp_dir.path();
|
||||
log::debug!("Created temporary directory: {}", cwd.display());
|
||||
|
||||
log::info!("Pulling package {} from Ubuntu {}...", package, series);
|
||||
crate::pull::pull(
|
||||
package,
|
||||
"",
|
||||
Some(series),
|
||||
"",
|
||||
"",
|
||||
Some("ubuntu"),
|
||||
Some(cwd),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Cannot pull package");
|
||||
log::info!("Successfully pulled package {}", package);
|
||||
|
||||
// Change directory to the package directory
|
||||
let cwd = cwd.join(package).join(package);
|
||||
log::debug!("Package directory: {}", cwd.display());
|
||||
|
||||
log::info!("Starting binary package build...");
|
||||
crate::deb::build_binary_package(arch, Some(series), Some(&cwd), cross, None)
|
||||
.expect("Cannot build binary package (deb)");
|
||||
log::info!("Successfully built binary package");
|
||||
|
||||
// Check that the .deb files are present
|
||||
let parent_dir = cwd.parent().expect("Cannot find parent directory");
|
||||
let deb_files = std::fs::read_dir(parent_dir)
|
||||
.expect("Cannot read build directory")
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "deb"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
log::info!("Found {} .deb files after build", deb_files.len());
|
||||
for file in &deb_files {
|
||||
log::debug!(" - {}", file.path().display());
|
||||
}
|
||||
|
||||
assert!(!deb_files.is_empty(), "No .deb files found after build");
|
||||
log::info!(
|
||||
"End-to-end test completed successfully for package: {}",
|
||||
package
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: We don't end-to-end test for a non-cross build for now.
|
||||
// This is an issue that we need to solve.
|
||||
// It seems that sbuild cannot by default be used inside of a (docker) container,
|
||||
// but our tests are currently running in one in CI.
|
||||
// TODO: Investigate on how to fix that.
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
async fn test_deb_hello_ubuntu_cross_end_to_end() {
|
||||
test_build_end_to_end("hello", "noble", Some("riscv64"), true).await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user