use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use clap::Args; use clap::Parser; use clap::Subcommand; use clap::ValueEnum; use crate::please_release::run_release_please; #[derive(Parser, Clone)] #[command(author, version, about, long_about = None, subcommand_required = true)] pub struct Command { #[command(subcommand)] commands: Commands, #[command(flatten)] global: GlobalArgs, } #[derive(Subcommand, Clone)] pub enum Commands { #[command(subcommand_required = true)] Local { #[command(subcommand)] command: LocalCommands, }, PullRequest {}, Main {}, Release, } #[derive(Subcommand, Clone)] pub enum LocalCommands { Test, PleaseRelease, } #[derive(Debug, Clone, ValueEnum)] pub enum BuildProfile { Debug, Release, } #[derive(Debug, Clone, Args)] pub struct GlobalArgs { #[arg(long, global = true, help_heading = "Global")] dry_run: bool, #[arg(long, global = true, help_heading = "Global")] rust_builder_image: Option, #[arg(long, global = true, help_heading = "Global")] source: Option, } #[tokio::main] async fn main() -> eyre::Result<()> { let _ = dotenv::dotenv(); let _ = color_eyre::install(); let client = dagger_sdk::connect().await?; let cli = Command::parse(); match &cli.commands { Commands::Local { command } => match command { LocalCommands::Test => { let base_image = base_rust_image(client.clone(), &cli.global, &None, &"debug".into()).await?; test::execute(client, &cli.global, base_image).await?; } LocalCommands::PleaseRelease => todo!(), }, Commands::PullRequest {} => { async fn test(client: Arc, cli: &Command) { let args = &cli.global; let base_image = base_rust_image(client.clone(), args, &None, &"debug".into()) .await .unwrap(); test::execute(client.clone(), args, base_image) .await .unwrap(); } tokio::join!(test(client.clone(), &cli),); } Commands::Main {} => { async fn test(client: Arc, cli: &Command) { let args = &cli.global; let base_image = base_rust_image(client.clone(), args, &None, &"debug".into()) .await .unwrap(); test::execute(client.clone(), args, base_image) .await .unwrap(); } async fn cuddle_please(client: Arc, cli: &Command) { run_release_please(client.clone(), &cli.global) .await .unwrap(); } tokio::join!( test(client.clone(), &cli), cuddle_please(client.clone(), &cli) ); } Commands::Release => todo!(), } Ok(()) } mod please_release { use std::sync::Arc; use dagger_cuddle_please::{models::CuddlePleaseSrcArgs, DaggerCuddlePleaseAction}; use crate::GlobalArgs; pub async fn run_release_please( client: Arc, args: &GlobalArgs, ) -> eyre::Result<()> { DaggerCuddlePleaseAction::dagger(client) .execute_src(&CuddlePleaseSrcArgs { cuddle_image: "kasperhermansen/cuddle-please:main-1691504183".into(), server: dagger_cuddle_please::models::SrcServer::Gitea { token: std::env::var("CUDDLE_PLEASE_TOKEN") .expect("CUDDLE_PLEASE_TOKEN to be present"), }, log_level: Some(dagger_cuddle_please::models::LogLevel::Debug), }) .await?; Ok(()) } } mod test { use std::sync::Arc; use crate::GlobalArgs; pub async fn execute( _client: Arc, _args: &GlobalArgs, container: dagger_sdk::Container, ) -> eyre::Result<()> { let test_image = container .pipeline("rust:test") .with_exec(vec!["apt", "update"]) .with_exec(vec!["apt", "install", "-y", "git"]) .with_exec(vec!["cargo", "test"]); let please_out = test_image.stdout().await?; println!("{please_out}"); let please_out = test_image.stderr().await?; println!("{please_out}"); test_image.exit_code().await?; Ok(()) } } pub fn get_src( client: Arc, args: &GlobalArgs, ) -> eyre::Result { let directory = client.host().directory_opts( args.source .clone() .unwrap_or(PathBuf::from(".")) .display() .to_string(), dagger_sdk::HostDirectoryOptsBuilder::default() .exclude(vec!["node_modules/", ".git/", "target/"]) .build()?, ); Ok(directory) } pub async fn get_rust_dep_src( client: Arc, args: &GlobalArgs, ) -> eyre::Result { let directory = client.host().directory_opts( args.source .clone() .unwrap_or(PathBuf::from(".")) .display() .to_string(), dagger_sdk::HostDirectoryOptsBuilder::default() .include(vec!["**/Cargo.toml", "**/Cargo.lock"]) .build()?, ); Ok(directory) } pub async fn get_rust_skeleton_files( client: Arc, _args: &GlobalArgs, ) -> eyre::Result<(dagger_sdk::Directory, Vec)> { let mut rust_crates = vec![PathBuf::from("ci")]; let mut dirs = tokio::fs::read_dir("crates").await?; while let Some(entry) = dirs.next_entry().await? { if entry.metadata().await?.is_dir() { rust_crates.push(entry.path()) } } let mut dirs = tokio::fs::read_dir("examples").await?; while let Some(entry) = dirs.next_entry().await? { if entry.metadata().await?.is_dir() { rust_crates.push(entry.path()) } } fn create_skeleton_files( directory: dagger_sdk::Directory, path: &Path, ) -> eyre::Result { println!("found crates: {}", path.display()); let main_content = r#" #[allow(dead_code)] fn main() { panic!("should never be executed"); }"#; let lib_content = r#" #[allow(dead_code)] fn some() { panic!("should never be executed"); }"#; let directory = directory.with_new_file( path.join("src").join("main.rs").display().to_string(), main_content, ); let directory = directory.with_new_file( path.join("src").join("lib.rs").display().to_string(), lib_content, ); Ok(directory) } let mut directory = client.directory(); let mut crate_names = Vec::new(); for rust_crate in rust_crates.iter() { if let Some(file_name) = rust_crate.file_name() { crate_names.push(file_name.to_str().unwrap().to_string()); } directory = create_skeleton_files(directory, rust_crate)?; } Ok((directory, crate_names)) } pub async fn base_rust_image( client: Arc, args: &GlobalArgs, platform: &Option, profile: &String, ) -> eyre::Result { let dep_src = get_rust_dep_src(client.clone(), args).await?; let (skeleton_files, crates) = get_rust_skeleton_files(client.clone(), args).await?; let src = get_src(client.clone(), args)?; let client = client.pipeline("rust_base_image"); let rust_target = match platform .clone() .unwrap_or("linux/amd64".to_string()) .as_str() { "linux/amd64" => "x86_64-unknown-linux-gnu", "linux/arm64" => "aarch64-unknown-linux-gnu", _ => eyre::bail!("architecture not supported"), }; let rust_build_image = client .container() .from( args.rust_builder_image .as_ref() .unwrap_or(&"rustlang/rust:nightly".into()), ) .with_exec(vec!["rustup", "target", "add", rust_target]) .with_exec(vec!["apt", "update"]) .with_exec(vec!["apt", "install", "-y", "jq"]); let target_cache = client.cache_volume(format!("rust_target_{}", profile)); let mut build_options = vec!["cargo", "build", "--target", rust_target, "--workspace"]; if profile == "release" { build_options.push("--release"); } let rust_prebuild = rust_build_image .with_workdir("/mnt/src") .with_directory("/mnt/src", dep_src.id().await?) .with_directory("/mnt/src/", skeleton_files.id().await?) .with_exec(build_options) .with_mounted_cache("/mnt/src/target/", target_cache.id().await?); let exclude = crates .iter() .filter(|c| **c != "ci") .map(|c| format!("**/*{}*", c.replace('-', "_"))) .collect::>(); let exclude = exclude.iter().map(|c| c.as_str()).collect(); let incremental_dir = client.directory().with_directory_opts( ".", rust_prebuild.directory("target").id().await?, dagger_sdk::DirectoryWithDirectoryOpts { exclude: Some(exclude), include: None, }, ); let rust_with_src = rust_build_image .with_workdir("/mnt/src") .with_directory( "/usr/local/cargo", rust_prebuild.directory("/usr/local/cargo").id().await?, ) .with_directory("target", incremental_dir.id().await?) .with_directory("/mnt/src/", src.id().await?); Ok(rust_with_src) }