use std::path::PathBuf; use crate::{ build::{BuildProfile, BuildTarget, RustVersion, SlimImage}, source::RustSource, }; pub struct HtmxBuild { client: dagger_sdk::Query, registry: Option, } impl HtmxBuild { pub fn new(client: dagger_sdk::Query) -> Self { Self { client, registry: None, } } pub async fn build( &self, source_path: Option>, rust_version: impl AsRef, profile: impl AsRef, crates: &[&str], extra_deps: &[&str], ) -> eyre::Result { let source_path = source_path.map(|s| s.into()).unwrap_or(PathBuf::from(".")); let rust_version = rust_version.as_ref(); let profile = profile.as_ref(); let rust_source = RustSource::new(self.client.clone()); let (src, dep_src) = rust_source .get_rust_src(Some(&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", "wasm32-unknown-unknown"]) .with_exec(vec!["apt", "update"]) .with_exec(deps) .with_exec(vec!["wget", "https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz"]) .with_exec("tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz".split_whitespace().collect()) .with_exec("mv cargo-binstall /usr/local/cargo/bin".split_whitespace().collect()) .with_exec(vec!["wget", "https://github.com/rui314/mold/releases/latest/download/mold-2.3.3-x86_64-linux.tar.gz"]) .with_exec("tar -xvf mold-2.3.3-x86_64-linux.tar.gz".split_whitespace().collect()) .with_exec("mv mold /usr/bin/mold".split_whitespace().collect()) .with_exec(vec!["cargo", "binstall", "sqlx-cli", "-y"]); let target_cache = self .client .cache_volume(format!("rust_htmx_{}", profile.to_string())); let build_options = vec!["cargo", "sqlx", "prepare"]; 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_path, 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 postgres_password = "somepassword123"; let postgres = self .client .container() .from("postgres:16.1") .with_env_variable("POSTGRES_PASSWORD", postgres_password); let postgres_service = postgres.with_exposed_port(5432); 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, BuildProfile::Release, crates, extra_deps, ) .await?; let binary_build = build_container .with_service_binding("postgres", postgres_service.as_service()) .with_env_variable( "DATABASE_URL", "root:somepassword123@postgres:5432/postgres", ) .with_exec(vec!["cargo", "sqlx", "migrate", "run"]) .with_exec(vec!["cargo", "sqlx", "prepare"]) .with_exec(vec!["cargo", "build", "--release", "--bin", bin_name]); self.build_debian_image( binary_build, 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, 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, builder_image: dagger_sdk::Container, 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_workdir("/mnt/app") .with_file( format!("/mnt/app/{bin_name}"), builder_image.file(format!("/mnt/src/target/release/{bin_name}")), ) .with_directory( "/mnt/app/target/site", builder_image.directory(format!("/mnt/src/target/site")), ) .with_file( "/mnt/app/Cargo.toml", builder_image.file(format!("/mnt/src/crates/{bin_name}/Cargo.toml")), ) .with_env_variable("RUST_LOG", "debug") .with_env_variable("APP_ENVIRONMENT", "production") .with_env_variable("LEPTOS_OUTPUT_NAME", bin_name) .with_env_variable("LEPTOS_SITE_ADDR", "0.0.0.0:8080") .with_env_variable("LEPTOS_SITE_ROOT", "site") .with_env_variable("LEPTOS_SITE_PKG_DIR", "pkg") .with_exposed_port(8080) .with_entrypoint(vec![format!("/mnt/app/{bin_name}")]); 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) } }