diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7994cb..b91b278 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -360,7 +360,7 @@ jobs: strategy: fail-fast: false matrix: - system: [freebsd, openbsd, netbsd] + system: [freebsd, openbsd, netbsd, dragonfly] steps: - uses: actions/checkout@v4 with: @@ -428,6 +428,23 @@ jobs: cp target/release/dcr "$ASSET" echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV" + # --- DRAGONFLYBSD --- + - name: Build in DragonFly BSD VM + if: matrix.system == 'dragonfly' + uses: vmactions/dragonflybsd-vm@v1 + with: + usesh: true + run: | + pkg install -y rust perl5 gmake + cargo build --release + + - name: Prepare DragonFly BSD Artifact + if: matrix.system == 'dragonfly' + run: | + ASSET="dcr-x86_64-unknown-dragonfly-${{ needs.setup.outputs.version }}" + cp target/release/dcr "$ASSET" + echo "BSD_ASSET=${ASSET}" >> "$GITHUB_ENV" + # --- UPLOAD TARGET --- - name: Upload BSD Asset to Release uses: softprops/action-gh-release@v2 diff --git a/src/core/builder/common.rs b/src/core/builder/common.rs index af956ce..527cc9c 100644 --- a/src/core/builder/common.rs +++ b/src/core/builder/common.rs @@ -316,6 +316,31 @@ pub fn needs_rebuild(source: &str, object: &str) -> bool { false } +pub fn needs_link(objects: &[String], output_path: &str) -> bool { + let out_meta = match fs::metadata(output_path) { + Ok(m) => m, + Err(_) => return true, + }; + let out_time = match out_meta.modified() { + Ok(t) => t, + Err(_) => return true, + }; + for obj in objects { + let obj_meta = match fs::metadata(obj) { + Ok(m) => m, + Err(_) => return true, + }; + let obj_time = match obj_meta.modified() { + Ok(t) => t, + Err(_) => return true, + }; + if obj_time > out_time { + return true; + } + } + false +} + fn parse_d_file(content: &str) -> Vec { let mut deps = Vec::new(); let text = content.replace("\\\n", " ").replace("\\\r\n", " "); @@ -418,6 +443,55 @@ mod tests { assert_eq!(result, "target/debug/obj/main.c.obj"); } + #[test] + fn needs_link_no_output() { + let dir = temp_dir("link_no_out"); + let obj1 = dir.join("test1.o"); + let obj2 = dir.join("test2.o"); + let out = dir.join("app"); + fs::write(&obj1, "").unwrap(); + fs::write(&obj2, "").unwrap(); + let objs = vec![ + obj1.to_string_lossy().to_string(), + obj2.to_string_lossy().to_string(), + ]; + assert!(needs_link(&objs, &out.to_string_lossy())); + } + + #[test] + fn needs_link_fresh() { + let dir = temp_dir("link_fresh"); + let obj1 = dir.join("test1.o"); + let obj2 = dir.join("test2.o"); + let out = dir.join("app"); + fs::write(&obj1, "").unwrap(); + fs::write(&obj2, "").unwrap(); + std::thread::sleep(std::time::Duration::from_millis(50)); + fs::write(&out, "").unwrap(); + let objs = vec![ + obj1.to_string_lossy().to_string(), + obj2.to_string_lossy().to_string(), + ]; + assert!(!needs_link(&objs, &out.to_string_lossy())); + } + + #[test] + fn needs_link_stale() { + let dir = temp_dir("link_stale"); + let obj1 = dir.join("test1.o"); + let obj2 = dir.join("test2.o"); + let out = dir.join("app"); + fs::write(&obj1, "").unwrap(); + fs::write(&out, "").unwrap(); + std::thread::sleep(std::time::Duration::from_millis(50)); + fs::write(&obj2, "").unwrap(); + let objs = vec![ + obj1.to_string_lossy().to_string(), + obj2.to_string_lossy().to_string(), + ]; + assert!(needs_link(&objs, &out.to_string_lossy())); + } + #[test] fn needs_rebuild_no_object() { let dir = temp_dir("rebuild_no_obj"); diff --git a/src/core/builder/gas.rs b/src/core/builder/gas.rs index 26f82a5..23406d5 100644 --- a/src/core/builder/gas.rs +++ b/src/core/builder/gas.rs @@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result { if ctx.kind == "staticlib" { let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + if !common::needs_link(&objects, &lib_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") { "lib" } else { @@ -105,6 +109,11 @@ pub fn build(ctx: &BuildContext) -> Result { } else { platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; + + if !common::needs_link(&objects, &out_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } cmd.arg("-o").arg(out_path); if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs index 55be925..110d61b 100644 --- a/src/core/builder/msvc.rs +++ b/src/core/builder/msvc.rs @@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result { if ctx.kind == "staticlib" { let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + if !common::needs_link(&objects, &lib_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } let mut cmd = Command::new(ctx.archiver.unwrap_or("lib")); cmd.arg("/nologo").arg(format!("/OUT:{lib_path}")); for obj in &objects { @@ -117,6 +121,11 @@ pub fn build(ctx: &BuildContext) -> Result { } else { platform::bin_path(ctx.profile, ctx.project_name, ctx.target_dir) }; + + if !common::needs_link(&objects, &out_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } cmd.arg(format!("/Fe:{out_path}")); if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { diff --git a/src/core/builder/nasm.rs b/src/core/builder/nasm.rs index b4c7458..0e4dc05 100644 --- a/src/core/builder/nasm.rs +++ b/src/core/builder/nasm.rs @@ -43,6 +43,10 @@ pub fn build(ctx: &BuildContext) -> Result { if ctx.kind == "staticlib" { let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + if !common::needs_link(&objects, &lib_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } let archiver = ctx.archiver.unwrap_or(if cfg!(target_os = "windows") { "lib" } else { @@ -105,6 +109,11 @@ pub fn build(ctx: &BuildContext) -> Result { } else { platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; + + if !common::needs_link(&objects, &out_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } cmd.arg("-o").arg(out_path); if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() { diff --git a/src/core/builder/unix_cc.rs b/src/core/builder/unix_cc.rs index 7f11d8f..eacc37f 100644 --- a/src/core/builder/unix_cc.rs +++ b/src/core/builder/unix_cc.rs @@ -45,6 +45,10 @@ pub fn build(ctx: &BuildContext) -> Result { if ctx.kind == "staticlib" { let lib_path = platform::lib_path(ctx.profile, ctx.project_name, ctx.target_dir); + if !common::needs_link(&objects, &lib_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } let mut cmd = Command::new(ctx.archiver.unwrap_or("ar")); cmd.arg("rcs").arg(&lib_path); for obj in &objects { @@ -106,6 +110,11 @@ pub fn build(ctx: &BuildContext) -> Result { } else { platform::bin_path(ctx.profile, &final_name, ctx.target_dir) }; + + if !common::needs_link(&objects, &out_path) { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + return Ok(elapsed); + } cmd.arg("-o").arg(out_path); if ctx.verbose || std::env::var("DCR_DEBUG").is_ok() {