From d4eab70e63f45db03159d1ae0dd781772ba0416f Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 30 Oct 2026 23:46:09 -0400 Subject: [PATCH] DWG bridge: build without acadrust on FreeBSD aarch64, where 0.6.6 doesn't compile Co-Authored-By: Claude Opus 5.5 --- crates/dwg/Cargo.toml | 5 +++- crates/dwg/build.rs | 12 +++++++++ crates/dwg/src/lib.rs | 60 +++++++++++++++++++++++-------------------- 3 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 crates/dwg/build.rs diff --git a/crates/dwg/Cargo.toml b/crates/dwg/Cargo.toml index a409e5b90..0be9fdf40 100644 --- a/crates/dwg/Cargo.toml +++ b/crates/dwg/Cargo.toml @@ -8,6 +8,9 @@ license.workspace = true [lints] workspace = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +# The same targets as build.rs's `no_dwg_bridge` leave acadrust out: wasm32, and FreeBSD aarch64, +# where acadrust 0.6.6 passes a `*const c_char` (u8 there) to its own `*const i8` sysctlbyname. +# 0.6.7 and 0.6.8 fix that but read some damaged DWGs as empty with no note (tests.rs, #800). +[target.'cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))'.dependencies] # MPL-2.0: used as an unmodified dependency only (see plan/adr/0001 and NOTICE). acadrust = "0.6.5" diff --git a/crates/dwg/build.rs b/crates/dwg/build.rs new file mode 100644 index 000000000..e97782ab4 --- /dev/null +++ b/crates/dwg/build.rs @@ -0,0 +1,12 @@ +//! Sets `no_dwg_bridge` on the targets built without acadrust (see Cargo.toml): the DWG functions +//! there return an error that says to use DXF. + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo::rustc-check-cfg=cfg(no_dwg_bridge)"); + let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + if arch == "wasm32" || (os == "freebsd" && arch == "aarch64") { + println!("cargo::rustc-cfg=no_dwg_bridge"); + } +} diff --git a/crates/dwg/src/lib.rs b/crates/dwg/src/lib.rs index f7bf13dd6..037aa26b5 100644 --- a/crates/dwg/src/lib.rs +++ b/crates/dwg/src/lib.rs @@ -58,13 +58,13 @@ impl Limits { } /// A byte count in megabytes for messages ("9.9 MB", "1024 MB"). -#[cfg_attr(target_arch = "wasm32", allow(dead_code))] +#[cfg_attr(no_dwg_bridge, allow(dead_code))] fn mb(n: u64) -> String { let m = n as f64 / f64::from(1u32 << 20); if m >= 100.0 { format!("{m:.0} MB") } else { format!("{m:.1} MB") } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(no_dwg_bridge))] mod native { use std::io::{Cursor, Write}; use std::panic::AssertUnwindSafe; @@ -741,18 +741,18 @@ mod native { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(no_dwg_bridge))] pub use native::{dwg_to_dxf, dwg_to_dxf_noted, dwg_to_dxf_with, dxf_to_dwg, dxf_to_dwg_as}; // Test-only: the stage ("post_process" or "convert") whose guard the current test checks. The // stage then panics on entry, so a test through the public API shows whether it is guarded. -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(all(test, not(no_dwg_bridge)))] thread_local! { static PANIC_IN: std::cell::Cell> = const { std::cell::Cell::new(None) }; } /// Panics when a test asked for `stage` to fail (see [`PANIC_IN`]); does nothing otherwise. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(no_dwg_bridge))] #[allow(clippy::panic)] fn fail_in_test(_stage: &str) { #[cfg(test)] @@ -762,31 +762,35 @@ fn fail_in_test(_stage: &str) { } /// The error for a DWG version CADCraft can't write. -#[cfg_attr(target_arch = "wasm32", allow(dead_code))] +#[cfg_attr(no_dwg_bridge, allow(dead_code))] fn unsupported(version: &str) -> String { let list: Vec<&str> = VERSIONS.iter().map(|(_, year)| *year).collect(); format!("DWG version `{version}` can't be written (CADCraft writes {})", list.join(", ")) } -#[cfg(target_arch = "wasm32")] +/// Which build has no DWG bridge, for the stubs' errors. +#[cfg(no_dwg_bridge)] +const NO_BRIDGE_BUILD: &str = if cfg!(target_arch = "wasm32") { "web" } else { "FreeBSD aarch64" }; + +#[cfg(no_dwg_bridge)] pub fn dwg_to_dxf(_bytes: &[u8]) -> Result, String> { - Err("DWG files can't be opened in the web build yet; save as DXF".into()) + Err(format!("DWG files can't be opened in the {NO_BRIDGE_BUILD} build yet; save as DXF")) } -#[cfg(target_arch = "wasm32")] +#[cfg(no_dwg_bridge)] pub fn dwg_to_dxf_noted(_bytes: &[u8], _limits: Limits) -> Result<(Vec, Vec), String> { - Err("DWG files can't be opened in the web build yet; save as DXF".into()) + Err(format!("DWG files can't be opened in the {NO_BRIDGE_BUILD} build yet; save as DXF")) } -#[cfg(target_arch = "wasm32")] +#[cfg(no_dwg_bridge)] pub fn dwg_to_dxf_with(_bytes: &[u8], _limits: Limits) -> Result, String> { - Err("DWG files can't be opened in the web build yet; save as DXF".into()) + Err(format!("DWG files can't be opened in the {NO_BRIDGE_BUILD} build yet; save as DXF")) } -#[cfg(target_arch = "wasm32")] +#[cfg(no_dwg_bridge)] pub fn dxf_to_dwg(_dxf: &[u8]) -> Result, String> { - Err("DWG files can't be written in the web build yet; save as DXF".into()) + Err(format!("DWG files can't be written in the {NO_BRIDGE_BUILD} build yet; save as DXF")) } -#[cfg(target_arch = "wasm32")] +#[cfg(no_dwg_bridge)] pub fn dxf_to_dwg_as(_dxf: &[u8], _version: &str) -> Result, String> { - Err("DWG files can't be written in the web build yet; save as DXF".into()) + Err(format!("DWG files can't be written in the {NO_BRIDGE_BUILD} build yet; save as DXF")) } #[cfg(test)] @@ -794,14 +798,14 @@ mod tests { use super::*; /// The DXF of an empty drawing, enough for the bridge to make a small DWG. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] const MINIMAL_DXF: &str = " 0\nSECTION\n 2\nHEADER\n 9\n$ACADVER\n 1\nAC1032\n 0\nENDSEC\n 0\nEOF\n"; /// Makes `stage` panic on the current thread until dropped. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] struct PanicIn; - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] impl PanicIn { fn at(stage: &'static str) -> Self { PANIC_IN.with(|p| p.set(Some(stage))); @@ -809,7 +813,7 @@ mod tests { } } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] impl Drop for PanicIn { fn drop(&mut self) { PANIC_IN.with(|p| p.set(None)); @@ -818,7 +822,7 @@ mod tests { /// The public read reports a panic in the post-read fix-ups as an error: this fails if the /// `guarded` call around `post_process` is taken out (#2402). - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] #[test] fn reading_a_dwg_survives_a_panic_in_post_processing() { let dwg = dxf_to_dwg(MINIMAL_DXF.as_bytes()).unwrap(); @@ -829,7 +833,7 @@ mod tests { } /// The DXF to DWG conversion reports a panic in its fix-ups as an error, not a crash (#2402). - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] #[test] fn converting_to_dwg_survives_a_panic_in_the_fix_ups() { let _panic = PanicIn::at("convert"); @@ -846,7 +850,7 @@ mod tests { /// A DXF with ROMANS as `$TEXTSTYLE` gives a DWG whose current text style is ROMANS, not /// Standard: the DXF names the style, and the handle read with it was Standard's (#1650). - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] #[test] fn current_text_style_reaches_the_dwg() { let header = " 0\nSECTION\n 2\nHEADER\n 9\n$ACADVER\n 1\nAC1032\n 9\n$TEXTSTYLE\n 7\nROMANS\n 0\nENDSEC\n"; @@ -866,7 +870,7 @@ mod tests { assert!(dwg_to_dxf(b"").is_err()); } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] #[test] fn dxf_to_dwg_rejects_garbage_without_crashing() { // The DXF→DWG path runs the third-party reader inside catch_unwind and reports failures @@ -876,7 +880,7 @@ mod tests { } /// A DXF with one DIMENSION per (text, group 70, subclass, points) row. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] fn dims_dxf(dims: &[(&str, i64, &str, &[(i32, [f64; 2])])]) -> String { let mut s = String::from(" 0\nSECTION\n 2\nHEADER\n 9\n$ACADVER\n 1\nAC1032\n 0\nENDSEC\n 0\nSECTION\n 2\nENTITIES\n"); for (text, flags, sub, pts) in dims { @@ -893,7 +897,7 @@ mod tests { /// the text is at its default position), and bit 32 in bit 1 (ODA DWG specification, common /// dimension data). The bytes are read back raw, so a reader that inverts bit 0 the same way /// as the writer can't hide a wrong byte. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] #[test] fn dimension_text_position_flags_reach_the_dwg_as_autocad_reads_them() { let linear: &[(i32, [f64; 2])] = &[(13, [0.0, 0.0]), (14, [100.0, 0.0])]; @@ -940,7 +944,7 @@ mod tests { } /// The handles a DXF text refers to or gives its records (groups 5, 105 and 330–369). - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] fn dxf_handles(dxf: &str) -> std::collections::HashSet { let lines: Vec<&str> = dxf.lines().collect(); lines @@ -957,7 +961,7 @@ mod tests { /// 1.2 GB DXF rendition over the limit, so it didn't open). They're left out with a note, and /// their dictionary entries with them; the definition and every component it reaches — /// through compound and point components, around a cycle — are kept. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(no_dwg_bridge))] #[test] fn orphaned_dgn_line_style_components_are_left_out() { use acadrust::objects::*;