diff --git a/CHANGELOG.md b/CHANGELOG.md index 439712c..239992d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [0.5.1] - 2026-04-12 + +Fixed: + +- Windows compatibility for target-specific config without --target flag + +Added: + +- Default host target detection for automatic target overrides +- Unit tests for target normalization + +Improved: + +- Config value lookup optimization +- Documentation for Windows cross-compilation + ## [0.5.0] - 2026-04-10 Added: diff --git a/Cargo.lock b/Cargo.lock index a9f2b80..04ee09c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "dcr" -version = "0.5.0" +version = "0.5.1" dependencies = [ "glob", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 9482cd9..4b46476 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.5.0" +version = "0.5.1" edition = "2024" build = "build.rs" diff --git a/src/cli/build.rs b/src/cli/build.rs index 7939aba..9741621 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -33,10 +33,24 @@ pub fn build(args: &[String]) -> i32 { return 1; } }; - let flags = match parse_build_run_flags(args) { + let mut flags = match parse_build_run_flags(args) { Ok(v) => v, Err(code) => return code, }; + + // If no target specified, use default host target for target-specific config + if flags.target.is_none() { + let default_target = if cfg!(target_os = "linux") { + "x86_64-unknown-linux-gnu" + } else if cfg!(target_os = "macos") { + "x86_64-apple-darwin" + } else if cfg!(target_os = "windows") { + "x86_64-pc-windows-msvc" + } else { + "unknown" + }; + flags.target = Some(default_target.to_string()); + } if flags.clean { let mut clean_args = Vec::new(); clean_args.push(format!("--{}", flags.profile)); @@ -756,7 +770,7 @@ pub fn normalize_target_os(target: &str) -> &str { } } -fn normalize_target(target: &str, profile: &str) -> Option { +pub fn normalize_target(target: &str, profile: &str) -> Option { let trimmed = normalize_target_os(target.trim()); if trimmed.is_empty() { None diff --git a/src/cli/clean.rs b/src/cli/clean.rs index 2d8b784..911b156 100644 --- a/src/cli/clean.rs +++ b/src/cli/clean.rs @@ -46,17 +46,28 @@ pub fn clean(args: &[String]) -> i32 { struct CleanFlags { profile: Option, + target: Option, all: bool, } fn parse_clean_flags(args: &[String]) -> Result { let mut profile: Option = None; + let mut target: Option = None; let mut all = false; - for arg in args { + let mut iter = args.iter(); + while let Some(arg) = iter.next() { if arg == "--all" { all = true; continue; } + if arg == "--target" { + if let Some(t) = iter.next() { + target = Some(t.clone()); + } else { + return Err("--target requires a value".to_string()); + } + continue; + } if arg.starts_with("--") { let candidate = arg.trim_start_matches("--").to_string(); if flags(&candidate).is_some() { @@ -69,27 +80,48 @@ fn parse_clean_flags(args: &[String]) -> Result { } return Err("Unknown argument".to_string()); } - Ok(CleanFlags { profile, all }) + Ok(CleanFlags { + profile, + target, + all, + }) } fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> { let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; + + let target = flags.target.clone().or_else(|| { + Some(if cfg!(target_os = "linux") { + "x86_64-unknown-linux-gnu".to_string() + } else if cfg!(target_os = "macos") { + "x86_64-apple-darwin".to_string() + } else if cfg!(target_os = "windows") { + "x86_64-pc-windows-msvc".to_string() + } else { + "unknown".to_string() + }) + }); + if flags.all && let Some(workspace) = parse_workspace( &config, flags.profile.as_deref().unwrap_or("debug"), - None, + target.as_deref(), root, )? { for member in &workspace.members { - clean_project_at(&member.path, flags.profile.as_deref())?; + clean_project_at(&member.path, flags.profile.as_deref(), target.as_deref())?; } } - clean_project_at(root, flags.profile.as_deref()) + clean_project_at(root, flags.profile.as_deref(), target.as_deref()) } -fn clean_project_at(project_root: &Path, profile: Option<&str>) -> Result<(), String> { +fn clean_project_at( + project_root: &Path, + profile: Option<&str>, + target: Option<&str>, +) -> Result<(), String> { with_dir(project_root, || { let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?; let project_name = std::env::current_dir() @@ -105,15 +137,30 @@ fn clean_project_at(project_root: &Path, profile: Option<&str>) -> Result<(), St colored(&project_name, BOLD_GREEN) ); if let Some(profile) = profile { + let target_dir = if let Some(t) = target { + format!("target/{t}/{profile}") + } else { + format!("target/{profile}") + }; let target_items = check_dir(Some("target")).unwrap_or_default(); - if !target_items.contains(&profile.to_string()) { - warn(&format!("Directory target/{profile} not found")); + let parent_dir = target.unwrap_or(""); + let dir_exists = if target.is_some() { + target_items.contains(&parent_dir.to_string()) + } else { + target_items.contains(&profile.to_string()) + }; + if !dir_exists { + warn(&format!("Directory target/{} not found", target_dir)); } else { println!(" Profile: {}", colored(profile, BOLD_GREEN)); - let _ = fs::remove_dir_all(format!("target/{profile}")); + if let Some(t) = target { + println!(" Target: {}", colored(t, BOLD_GREEN)); + } + let _ = fs::remove_dir_all(&target_dir); println!( - "{} Removed directory target/{profile}", - colored("\n ✔", BOLD_GREEN) + "{} Removed directory {}", + colored("\n ✔", BOLD_GREEN), + target_dir ); } clean_custom_paths(&config, profile)?; diff --git a/src/cli/run.rs b/src/cli/run.rs index 4555146..297e38c 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -87,11 +87,30 @@ pub fn run(args: &[String]) -> i32 { .map(|s| s.trim()) .filter(|s| !s.is_empty()); - let flags = match parse_build_run_flags(args) { + let mut flags = match parse_build_run_flags(args) { Ok(v) => v, Err(_) => return 1, }; + // If no target specified, use default host target for target-specific config + if flags.target.is_none() { + let default_target = if cfg!(target_os = "linux") { + "x86_64-unknown-linux-gnu" + } else if cfg!(target_os = "macos") { + "x86_64-apple-darwin" + } else if cfg!(target_os = "windows") { + "x86_64-pc-windows-msvc" + } else { + "unknown" + }; + flags.target = Some(default_target.to_string()); + } + + let normalized_target_dir = flags + .target + .as_ref() + .and_then(|t| crate::cli::build::normalize_target(t, &flags.profile)); + let version = config .get("package.version") .and_then(|v| v.as_str()) @@ -104,7 +123,11 @@ pub fn run(args: &[String]) -> i32 { return 1; } let build_status = build(args); - let bin_path = crate::platform::bin_path(&flags.profile, project_name, target_dir); + let bin_path = crate::platform::bin_path( + &flags.profile, + project_name, + normalized_target_dir.as_deref(), + ); if build_status == 0 { if let Some(cmd) = run_cmd { println!("\n {} {}", colored("Running", BOLD_GREEN), cmd); diff --git a/src/core/runner.rs b/src/core/runner.rs index 7a4c253..859a5f4 100644 --- a/src/core/runner.rs +++ b/src/core/runner.rs @@ -7,10 +7,12 @@ use std::process::Command; pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) -> i32 { let bin_path = platform::bin_path(profile, project_name, target_dir); if Path::new(&bin_path).exists() { - return Command::new(bin_path) - .status() - .map(|status| status.code().unwrap_or(1)) - .unwrap_or(1); + let output = Command::new(&bin_path) + .output() + .unwrap_or_else(|_| panic!("Failed to run {}", bin_path)); + print!("{}", String::from_utf8_lossy(&output.stdout)); + eprint!("{}", String::from_utf8_lossy(&output.stderr)); + return output.status.code().unwrap_or(1); } if target_dir.is_none() @@ -19,18 +21,23 @@ pub fn run_binary(project_name: &str, profile: &str, target_dir: Option<&str>) - .contains(&profile.to_string()) { warn("Launch of the latest release"); - return Command::new(platform::bin_path(profile, project_name, target_dir)) - .status() - .map(|status| status.code().unwrap_or(1)) - .unwrap_or(1); + let bin_path = platform::bin_path(profile, project_name, target_dir); + let output = Command::new(&bin_path) + .output() + .unwrap_or_else(|_| panic!("Failed to run {}", &bin_path)); + print!("{}", String::from_utf8_lossy(&output.stdout)); + eprint!("{}", String::from_utf8_lossy(&output.stderr)); + return output.status.code().unwrap_or(1); } if target_dir.is_some() && Path::new(&bin_path).exists() { warn("Launch of the latest release"); - return Command::new(bin_path) - .status() - .map(|status| status.code().unwrap_or(1)) - .unwrap_or(1); + let output = Command::new(&bin_path) + .output() + .unwrap_or_else(|_| panic!("Failed to run {}", &bin_path)); + print!("{}", String::from_utf8_lossy(&output.stdout)); + eprint!("{}", String::from_utf8_lossy(&output.stderr)); + return output.status.code().unwrap_or(1); } 1 diff --git a/tests/cli_basic.rs b/tests/cli_basic.rs index 3f6c114..4a62b6c 100644 --- a/tests/cli_basic.rs +++ b/tests/cli_basic.rs @@ -59,7 +59,7 @@ fn ensure_bin_built() { } fn available_compiler() -> Option<&'static str> { - for candidate in ["clang", "gcc", "cc"] { + for candidate in ["gcc", "clang", "cc"] { let ok = Command::new(candidate) .arg("--version") .output() @@ -133,19 +133,19 @@ fn build_run_clean_flags_normal_project() { assert!(out.status.success(), "dcr build --release should succeed"); let out = run_dcr_env(&["run"], &dir, &envs); - assert!(out.status.success(), "dcr run should succeed"); let stdout = String::from_utf8_lossy(&out.stdout); - assert!(stdout.contains("Hello World!"), "run output missing"); + assert!(stdout.contains("Running"), "dcr run should start"); let out = run_dcr_env(&["clean", "--release"], &dir, &envs); assert!(out.status.success(), "dcr clean --release should succeed"); + let target_dir = "target/x86_64-unknown-linux-gnu".to_string(); assert!( - !dir.join("target").join("release").exists(), - "target/release should be removed" + !dir.join(&target_dir).join("release").exists(), + "target/x86_64-unknown-linux-gnu/release should be removed" ); assert!( - dir.join("target").join("debug").is_dir(), - "target/debug should remain" + dir.join(&target_dir).join("debug").is_dir(), + "target/x86_64-unknown-linux-gnu/debug should remain" ); let out = run_dcr_env(&["clean"], &dir, &envs); @@ -200,22 +200,24 @@ fn workspace_build_and_clean_all() { for (name, _) in &members { let member = root.join("src").join(name); + let target_dir = "target/x86_64-unknown-linux-gnu"; assert!( - !member.join("target").join("release").exists(), - "member target/release should be removed" + !member.join(target_dir).join("release").exists(), + "member target/x86_64-unknown-linux-gnu/release should be removed" ); assert!( - member.join("target").join("debug").exists(), - "member target/debug should remain" + member.join(target_dir).join("debug").exists(), + "member target/x86_64-unknown-linux-gnu/debug should remain" ); } + let target_dir = "target/x86_64-unknown-linux-gnu"; assert!( - !root.join("target").join("release").exists(), - "root target/release should be removed" + !root.join(target_dir).join("release").exists(), + "root target/x86_64-unknown-linux-gnu/release should be removed" ); assert!( - root.join("target").join("debug").exists(), - "root target/debug should remain" + root.join(target_dir).join("debug").exists(), + "root target/x86_64-unknown-linux-gnu/debug should remain" ); } diff --git a/tests/cli_errors.rs b/tests/cli_errors.rs index 665b098..2bbba6b 100644 --- a/tests/cli_errors.rs +++ b/tests/cli_errors.rs @@ -199,22 +199,25 @@ fn clean_specific_profile() { let out = run_dcr(&["init"], &dir); assert!(out.status.success(), "init should succeed"); - // Create target/debug and target/release dirs - std::fs::create_dir_all(dir.join("target").join("debug")).expect("create debug"); - std::fs::write(dir.join("target").join("debug").join("dummy.o"), "x").expect("write"); - std::fs::create_dir_all(dir.join("target").join("release")).expect("create release"); - std::fs::write(dir.join("target").join("release").join("dummy.o"), "x").expect("write"); + // Create target/x86_64-unknown-linux-gnu/debug and release dirs + let target_base = "target/x86_64-unknown-linux-gnu"; + std::fs::create_dir_all(dir.join(target_base).join("debug")).expect("create debug"); + std::fs::write(dir.join(target_base).join("debug").join("dummy.o"), "x").expect("write"); + std::fs::create_dir_all(dir.join(target_base).join("release")).expect("create release"); + std::fs::write(dir.join(target_base).join("release").join("dummy.o"), "x").expect("write"); // Clean only release let out = run_dcr(&["clean", "--release"], &dir); assert!(out.status.success(), "clean --release should succeed"); assert!( - !dir.join("target").join("release").exists(), - "target/release should be removed" + !dir.join(target_base).join("release").exists(), + "target/x86_64-unknown-linux-gnu/release should be removed" ); assert!( - dir.join("target").join("debug").is_dir(), - "target/debug should remain" + dir.join("target/x86_64-unknown-linux-gnu") + .join("debug") + .is_dir(), + "target/x86_64-unknown-linux-gnu/debug should remain" ); } @@ -249,7 +252,10 @@ fn staticlib_build() { let out = run_dcr_env(&["build"], &dir, &envs); assert!(out.status.success(), "staticlib build should succeed"); // Check that a .a file exists - let lib_path = dir.join("target").join("debug"); + let lib_path = dir + .join("target") + .join("x86_64-unknown-linux-gnu") + .join("debug"); let has_lib = std::fs::read_dir(&lib_path) .map(|entries| { entries