185 lines
6.1 KiB
Rust
185 lines
6.1 KiB
Rust
mod cross;
|
|
mod ephemeral;
|
|
mod local;
|
|
mod sbuild;
|
|
|
|
use crate::context;
|
|
use std::error::Error;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
/// Build mode for the binary build
|
|
#[derive(PartialEq)]
|
|
pub enum BuildMode {
|
|
/// Use `sbuild` for the build, configured in unshare mode
|
|
Sbuild,
|
|
/// Local build, directly on the context
|
|
Local,
|
|
}
|
|
|
|
/// Build package in 'cwd' to a .deb
|
|
pub fn build_binary_package(
|
|
arch: Option<&str>,
|
|
series: Option<&str>,
|
|
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 {
|
|
// By default, we use local build
|
|
BuildMode::Local
|
|
};
|
|
|
|
// Create an ephemeral unshare context for all Local builds
|
|
let _guard = if mode == BuildMode::Local {
|
|
Some(ephemeral::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);
|
|
|
|
// Check if the .dsc file exists in current context
|
|
let ctx = context::current();
|
|
if !ctx.exists(&dsc_path)? {
|
|
return Err(format!("Could not find .dsc file at {}", dsc_path.display()).into());
|
|
}
|
|
Ok(dsc_path)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use serial_test::serial;
|
|
async fn test_build_end_to_end(
|
|
package: &str,
|
|
series: &str,
|
|
dist: Option<&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), "", "", dist, 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
|
|
);
|
|
}
|
|
|
|
// Tests below will be marked 'serial'
|
|
// As builds are using ephemeral contexts, tests running on the same
|
|
// process could use the ephemeral context of another thread and
|
|
// interfere with each other.
|
|
// FIXME: This is not ideal. In the future, we might want to
|
|
// either explicitely pass context (instead of shared state) or
|
|
// fork for building?
|
|
#[tokio::test]
|
|
#[test_log::test]
|
|
#[serial]
|
|
async fn test_deb_hello_ubuntu_end_to_end() {
|
|
test_build_end_to_end("hello", "noble", None, None, false).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[test_log::test]
|
|
#[cfg(target_arch = "x86_64")]
|
|
#[serial]
|
|
async fn test_deb_hello_ubuntu_cross_end_to_end() {
|
|
test_build_end_to_end("hello", "noble", None, Some("riscv64"), true).await;
|
|
}
|
|
}
|