use std::path::PathBuf; use crate::source::RustSource; #[allow(dead_code)] pub struct RustBuild { client: dagger_sdk::Query, registry: Option, } impl RustBuild { pub fn new(client: dagger_sdk::Query) -> Self { Self { client, registry: None, } } pub async fn build( &self, source_path: Option>, rust_version: impl AsRef, target: impl AsRef, profile: impl AsRef, crates: &[&str], extra_deps: &[&str], ) -> eyre::Result { let rust_version = rust_version.as_ref(); let target = target.as_ref(); let profile = profile.as_ref(); let source_path = source_path.map(|s| s.into()); let source = source_path.clone().unwrap_or(PathBuf::from(".")); let rust_source = RustSource::new(self.client.clone()); let (src, dep_src) = rust_source .get_rust_src(source_path, crates.to_vec()) .await?; let mut deps = vec!["apt", "install", "-y"]; deps.extend(extra_deps); let rust_build_image = self .client .container() .from(rust_version.to_string()) .with_exec(vec!["rustup", "target", "add", &target.to_string()]) .with_exec(vec!["apt", "update"]) .with_exec(deps); let target_cache = self.client.cache_volume(format!( "rust_target_{}_{}", profile.to_string(), target.to_string() )); let target_str = target.to_string(); let mut build_options = vec!["cargo", "build", "--target", &target_str, "--workspace"]; if matches!(profile, BuildProfile::Release) { build_options.push("--release"); } let rust_prebuild = rust_build_image .with_workdir("/mnt/src") .with_directory("/mnt/src", dep_src) .with_exec(build_options) .with_mounted_cache("/mnt/src/target/", target_cache); let incremental_dir = rust_source .get_rust_target_src(&source, rust_prebuild.clone(), crates.to_vec()) .await?; let rust_with_src = rust_build_image .with_workdir("/mnt/src") .with_directory( "/usr/local/cargo", rust_prebuild.directory("/usr/local/cargo"), ) .with_directory("/mnt/src/target", incremental_dir) .with_directory("/mnt/src/", src); Ok(rust_with_src) } pub async fn build_release( &self, source_path: Option>, rust_version: impl AsRef, crates: &[&str], extra_deps: &[&str], images: impl IntoIterator, bin_name: &str, ) -> eyre::Result> { let images = images.into_iter().collect::>(); let source_path = source_path.map(|s| s.into()); let mut containers = Vec::new(); for container_image in images { let container = match &container_image { SlimImage::Debian { image, deps, .. } => { let target = BuildTarget::from_target(&container_image); let build_container = self .build( source_path.clone(), &rust_version, BuildTarget::from_target(&container_image), BuildProfile::Release, crates, extra_deps, ) .await?; let bin = build_container .with_exec(vec![ "cargo", "build", "--target", &target.to_string(), "--release", "-p", bin_name, ]) .file(format!( "target/{}/release/{}", target.to_string(), bin_name )); self.build_debian_image( bin, image, BuildTarget::from_target(&container_image), deps.iter() .map(|d| d.as_str()) .collect::>() .as_slice(), bin_name, ) .await? } SlimImage::Alpine { image, deps, .. } => { let target = BuildTarget::from_target(&container_image); let build_container = self .build( source_path.clone(), &rust_version, BuildTarget::from_target(&container_image), BuildProfile::Release, crates, extra_deps, ) .await?; let bin = build_container .with_exec(vec![ "cargo", "build", "--target", &target.to_string(), "--release", "-p", bin_name, ]) .file(format!( "target/{}/release/{}", target.to_string(), bin_name )); self.build_alpine_image( bin, image, BuildTarget::from_target(&container_image), deps.iter() .map(|d| d.as_str()) .collect::>() .as_slice(), bin_name, ) .await? } }; containers.push(container); } Ok(containers) } async fn build_debian_image( &self, bin: dagger_sdk::File, image: &str, target: BuildTarget, production_deps: &[&str], bin_name: &str, ) -> eyre::Result { let base_debian = self .client .container_opts(dagger_sdk::QueryContainerOpts { id: None, platform: Some(target.into_platform()), }) .from(image); let mut packages = vec!["apt", "install", "-y"]; packages.extend_from_slice(production_deps); let base_debian = base_debian .with_exec(vec!["apt", "update"]) .with_exec(packages); let final_image = base_debian .with_file(format!("/usr/local/bin/{}", bin_name), bin) .with_exec(vec![bin_name, "--help"]); final_image.sync().await?; Ok(final_image) } async fn build_alpine_image( &self, bin: dagger_sdk::File, image: &str, target: BuildTarget, production_deps: &[&str], bin_name: &str, ) -> eyre::Result { let base_debian = self .client .container_opts(dagger_sdk::QueryContainerOpts { id: None, platform: Some(target.into_platform()), }) .from(image); let mut packages = vec!["apk", "add"]; packages.extend_from_slice(production_deps); let base_debian = base_debian.with_exec(packages); let final_image = base_debian.with_file(format!("/usr/local/bin/{}", bin_name), bin); Ok(final_image) } } pub enum RustVersion { Nightly, Stable(String), } impl AsRef for RustVersion { fn as_ref(&self) -> &RustVersion { self } } impl ToString for RustVersion { fn to_string(&self) -> String { match self { RustVersion::Nightly => "rustlang/rust:nightly".to_string(), RustVersion::Stable(version) => format!("rust:{}", version), } } } pub enum BuildTarget { LinuxAmd64, LinuxArm64, LinuxAmd64Musl, LinuxArm64Musl, MacOSAmd64, MacOSArm64, } impl BuildTarget { pub fn from_target(image: &SlimImage) -> Self { match image { SlimImage::Debian { architecture, .. } => match architecture { BuildArchitecture::Amd64 => Self::LinuxAmd64, BuildArchitecture::Arm64 => Self::LinuxArm64, }, SlimImage::Alpine { architecture, .. } => match architecture { BuildArchitecture::Amd64 => Self::LinuxAmd64Musl, BuildArchitecture::Arm64 => Self::LinuxArm64Musl, }, } } pub fn into_platform(&self) -> dagger_sdk::Platform { let platform = match self { BuildTarget::LinuxAmd64 => "linux/amd64", BuildTarget::LinuxArm64 => "linux/arm64", BuildTarget::LinuxAmd64Musl => "linux/amd64", BuildTarget::LinuxArm64Musl => "linux/arm64", BuildTarget::MacOSAmd64 => "darwin/amd64", BuildTarget::MacOSArm64 => "darwin/arm64", }; dagger_sdk::Platform(platform.into()) } } impl AsRef for BuildTarget { fn as_ref(&self) -> &BuildTarget { self } } impl ToString for BuildTarget { fn to_string(&self) -> String { let target = match self { BuildTarget::LinuxAmd64 => "x86_64-unknown-linux-gnu", BuildTarget::LinuxArm64 => "aarch64-unknown-linux-gnu", BuildTarget::LinuxAmd64Musl => "x86_64-unknown-linux-musl", BuildTarget::LinuxArm64Musl => "aarch64-unknown-linux-musl", BuildTarget::MacOSAmd64 => "x86_64-apple-darwin", BuildTarget::MacOSArm64 => "aarch64-apple-darwin", }; target.into() } } pub enum BuildProfile { Debug, Release, } impl AsRef for BuildProfile { fn as_ref(&self) -> &BuildProfile { self } } impl ToString for BuildProfile { fn to_string(&self) -> String { let profile = match self { BuildProfile::Debug => "debug", BuildProfile::Release => "release", }; profile.into() } } pub enum SlimImage { Debian { image: String, deps: Vec, architecture: BuildArchitecture, }, Alpine { image: String, deps: Vec, architecture: BuildArchitecture, }, } pub enum BuildArchitecture { Amd64, Arm64, }