From 9ec57281591d34dcb39e7e3916fc18375880b340 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:11:26 +0100 Subject: [PATCH 1/2] Fix FreeBSD ARM64 test dependency gating --- crates/io/Cargo.toml | 7 ++++-- crates/io/src/tests.rs | 6 ++--- crates/io/tests/dwg_active_layout.rs | 2 ++ crates/io/tests/dwg_kept_object_versions.rs | 2 +- crates/io/tests/dwg_polyline_vertex_order.rs | 2 +- crates/io/tests/dwg_required_dictionaries.rs | 2 +- crates/io/tests/dwg_table_cell_height.rs | 2 +- crates/io/tests/dwg_unavailable.rs | 21 ++++++++++++++++ crates/io/tests/it/dim_block_text.rs | 2 +- crates/io/tests/it/dwg_layout_owner.rs | 2 ++ crates/io/tests/it/dynblock_read.rs | 2 ++ crates/io/tests/it/mtext_caret_spaces.rs | 4 ++-- crates/io/tests/it/roundtrip/mod.rs | 2 +- xtask/tests/dwg_dependency_targets.rs | 25 ++++++++++++++++++++ 14 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 crates/io/tests/dwg_unavailable.rs create mode 100644 xtask/tests/dwg_dependency_targets.rs diff --git a/crates/io/Cargo.toml b/crates/io/Cargo.toml index 21ba5d5cc..f39a630f1 100644 --- a/crates/io/Cargo.toml +++ b/crates/io/Cargo.toml @@ -25,8 +25,11 @@ quick-xml = { workspace = true } image = { workspace = true } [dev-dependencies] -# Reads CADCraft's DWG output as written (tests only; the same version as cadcraft-dwg's). -acadrust = "0.6.6" tiny-skia = { workspace = true } # Reads plotted PDFs back. craft-pdf = { workspace = true, features = ["read"] } + +# Inspect DWG output only where cadcraft-dwg builds its native bridge. An unconditional +# dev-dependency also builds acadrust during --all-targets, breaking FreeBSD aarch64 (#2526). +[target.'cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))'.dev-dependencies] +acadrust = "0.6.6" diff --git a/crates/io/src/tests.rs b/crates/io/src/tests.rs index 0a10424bc..040e71927 100644 --- a/crates/io/src/tests.rs +++ b/crates/io/src/tests.rs @@ -3511,7 +3511,7 @@ fn proxies_keep_their_class_when_classes_are_rewritten() { /// classes there: a 2018 drawing's piping proxies came out as LARGE_RADIAL_DIMENSION and /// SECTIONOBJECT, and the ODA File Converter refused the file ("AcDbProxyObject can't be cast to /// AcDbBlockTableRecord"). A DWG before 2013 leaves them out (acadrust writes no class for them). -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn proxies_keep_their_class_in_dwg() { use acadrust::entities::{EntityType, ExtendedEntityData}; @@ -3553,7 +3553,7 @@ fn proxies_keep_their_class_in_dwg() { /// its CLASSES ([`proxy_file`]'s "Stray", 537): kept with that number it would name some other /// class of the DWG, so the DWG leaves it out, as CADCraft's own save does, and keeps the proxies /// whose classes it can name. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn a_proxy_whose_class_is_unknown_is_left_out_of_the_dwg() { use acadrust::entities::{EntityType, ExtendedEntityData}; @@ -3602,7 +3602,7 @@ fn a_proxy_left_out_of_the_dwg_is_counted_as_unwritten() { /// numbers can't be mapped to the DWG's classes. Kept as they were, they named other classes /// (the ODA refuses such a file), so the DWG leaves them out, as a pre-2013 DWG does, and the /// rest of the drawing is written. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn a_binary_dxf_leaves_its_proxies_out_of_the_dwg() { use acadrust::entities::{EntityType, ExtendedEntityData}; diff --git a/crates/io/tests/dwg_active_layout.rs b/crates/io/tests/dwg_active_layout.rs index 5093b2f5f..fd7d594ed 100644 --- a/crates/io/tests/dwg_active_layout.rs +++ b/crates/io/tests/dwg_active_layout.rs @@ -2,6 +2,8 @@ //! the one written as `*Paper_Space` (the DWG header's paper space block), the others //! `*Paper_SpaceN`. Saving used to make the first tab the active one again. +#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] + use std::io::Cursor; use cadcraft_doc::{Common, Drawing, EntityKind, Layout, Line, Space}; diff --git a/crates/io/tests/dwg_kept_object_versions.rs b/crates/io/tests/dwg_kept_object_versions.rs index 787470705..5cb39919f 100644 --- a/crates/io/tests/dwg_kept_object_versions.rs +++ b/crates/io/tests/dwg_kept_object_versions.rs @@ -11,7 +11,7 @@ //! the same DWG version (an older DWG can't hold everything: a 2004 field has no value flags or //! format), read back; CADCraft's DWG must hold the same objects. When the DWG is the DXF's own //! version, that reference is also checked against the DXF as read. -#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] use std::fmt::Write as _; use std::io::Cursor; diff --git a/crates/io/tests/dwg_polyline_vertex_order.rs b/crates/io/tests/dwg_polyline_vertex_order.rs index 5cb63d2f6..b9606ded5 100644 --- a/crates/io/tests/dwg_polyline_vertex_order.rs +++ b/crates/io/tests/dwg_polyline_vertex_order.rs @@ -4,7 +4,7 @@ //! //! The DWG is written here with acadrust, giving the second 3D-polyline vertex the highest //! handle: exactly what an editing program produces when it inserts a vertex. -#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] use acadrust::entities::{EntityType, Polyline3D, Vertex3DPolyline}; use acadrust::types::{DxfVersion, Handle, Vector3}; diff --git a/crates/io/tests/dwg_required_dictionaries.rs b/crates/io/tests/dwg_required_dictionaries.rs index 3feb0d7dd..f28be9618 100644 --- a/crates/io/tests/dwg_required_dictionaries.rs +++ b/crates/io/tests/dwg_required_dictionaries.rs @@ -3,7 +3,7 @@ //! it as empty ("No data was imported..."), though it reads the same drawing as DXF (and as an //! AC1032 DWG) fine. The DWG bridge adds the dictionary AutoCAD writes, with its usual variables, //! when the DXF has none. -#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] use std::io::Cursor; diff --git a/crates/io/tests/dwg_table_cell_height.rs b/crates/io/tests/dwg_table_cell_height.rs index 11ee98c9d..6e077b870 100644 --- a/crates/io/tests/dwg_table_cell_height.rs +++ b/crates/io/tests/dwg_table_cell_height.rs @@ -1,5 +1,5 @@ //! TABLE heights inherit from the table style unless the file records a cell override. -#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] use cadcraft_doc::{Common, Drawing, EntityKind, Space, Table, TableCell, TableStyle}; diff --git a/crates/io/tests/dwg_unavailable.rs b/crates/io/tests/dwg_unavailable.rs new file mode 100644 index 000000000..421aded7c --- /dev/null +++ b/crates/io/tests/dwg_unavailable.rs @@ -0,0 +1,21 @@ +//! The targets without the native DWG bridge still handle DXF and report an actionable error +//! for DWG instead of pretending that opening or saving succeeded. + +#![cfg(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64")))] + +use cadcraft_doc::Drawing; +use cadcraft_io::{read, write}; + +#[test] +fn no_dwg_bridge_preserves_dxf_and_reports_dwg_unavailable() { + let drawing = Drawing::new_metric(); + let dxf = write(&drawing, "drawing.dxf").expect("DXF remains available"); + assert!(read(&dxf, "drawing.dxf").is_ok()); + + let read_error = read(b"AC1032unavailable", "drawing.dwg").unwrap_err().to_string(); + let write_error = write(&drawing, "drawing.dwg").unwrap_err().to_string(); + for error in [read_error, write_error] { + assert!(error.contains("DWG"), "{error}"); + assert!(error.contains("DXF"), "{error}"); + } +} diff --git a/crates/io/tests/it/dim_block_text.rs b/crates/io/tests/it/dim_block_text.rs index 244e9401c..8a6225a93 100644 --- a/crates/io/tests/it/dim_block_text.rs +++ b/crates/io/tests/it/dim_block_text.rs @@ -103,7 +103,7 @@ fn dxf_angular_block_text_keeps_the_decimal_separator() { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn dwg_angular_block_text_keeps_the_decimal_separator() { use std::io::Cursor; diff --git a/crates/io/tests/it/dwg_layout_owner.rs b/crates/io/tests/it/dwg_layout_owner.rs index 45de12cb8..20dfa72c3 100644 --- a/crates/io/tests/it/dwg_layout_owner.rs +++ b/crates/io/tests/it/dwg_layout_owner.rs @@ -2,6 +2,8 @@ //! by their own block (entity mode 0 and its handle). Written as "paper space" (mode 1), AutoCAD //! put them on the first layout and left theirs empty. +#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] + use std::io::Cursor; use cadcraft_doc::{Circle, Common, Drawing, EntityKind, Layout, Line, Space}; diff --git a/crates/io/tests/it/dynblock_read.rs b/crates/io/tests/it/dynblock_read.rs index 01e67069f..fbcc74e35 100644 --- a/crates/io/tests/it/dynblock_read.rs +++ b/crates/io/tests/it/dynblock_read.rs @@ -87,6 +87,7 @@ fn a_saved_drawing_keeps_the_dynamic_block_as_read() { } /// The Door fixture with a scale action on the Width parameter: graph node 6, object 68. +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] fn with_scale_action() -> String { let node = "91 6\n93 32\n95 7\n360 68\n92 -1\n92 -1\n92 -1\n92 -1\n"; let action = "0 BLOCKSCALEACTION\n5 68\n330 61\n100 AcDbEvalExpr\n90 7\n98 33\n99 232\n\ @@ -105,6 +106,7 @@ fn with_scale_action() -> String { /// the DWG holds it with its connections, and it reads back as the same action. AutoCAD refused /// such DWGs while acadrust left out the byte after its connections (cadcraft-dwg's /// `scale_actions_keep_the_byte_autocad_reads_after_their_connections` covers the byte). +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn a_scale_action_survives_a_dwg_save() { use acadrust::objects::{DynamicBlockData, DynamicBlockObject, ObjectType}; diff --git a/crates/io/tests/it/mtext_caret_spaces.rs b/crates/io/tests/it/mtext_caret_spaces.rs index 8bb1ed071..92b6ffd9a 100644 --- a/crates/io/tests/it/mtext_caret_spaces.rs +++ b/crates/io/tests/it/mtext_caret_spaces.rs @@ -74,7 +74,7 @@ fn dxf_mtext_caret_spaces_round_trip() -> TestResult { Ok(()) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn dwg_mtext_caret_spaces_round_trip() -> TestResult { let (original, expected) = drawing()?; @@ -103,7 +103,7 @@ fn dwg_mtext_caret_spaces_round_trip() -> TestResult { Ok(()) } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] #[test] fn bridge_caret_space_is_escaped_once_in_each_direction() -> TestResult { let (d, _) = drawing()?; diff --git a/crates/io/tests/it/roundtrip/mod.rs b/crates/io/tests/it/roundtrip/mod.rs index d38f84eb1..02a35c808 100644 --- a/crates/io/tests/it/roundtrip/mod.rs +++ b/crates/io/tests/it/roundtrip/mod.rs @@ -4,7 +4,7 @@ mod cases; mod collections; mod compare; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] mod dwg; mod dxf; mod handle_limits; diff --git a/xtask/tests/dwg_dependency_targets.rs b/xtask/tests/dwg_dependency_targets.rs new file mode 100644 index 000000000..a25b80bb5 --- /dev/null +++ b/xtask/tests/dwg_dependency_targets.rs @@ -0,0 +1,25 @@ +//! Test builds must not reintroduce the DWG decoder on targets where the bridge excludes it. + +use serde_json::Value; +use std::process::Command; + +#[test] +fn dwg_test_dependency_matches_the_bridge_targets() { + let output = Command::new(env!("CARGO")) + .args(["metadata", "--format-version", "1", "--no-deps", "--locked"]) + .current_dir(env!("CARGO_MANIFEST_DIR")) + .output() + .expect("read workspace dependency metadata"); + assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); + let metadata: Value = serde_json::from_slice(&output.stdout).expect("Cargo returns JSON"); + let packages = metadata["packages"].as_array().expect("workspace packages"); + let dependency = |name: &str| { + let package = packages.iter().find(|p| p["name"] == name).expect("workspace crate"); + package["dependencies"].as_array().expect("crate dependencies").iter().find(|d| d["name"] == "acadrust").expect("DWG decoder dependency") + }; + let bridge = dependency("cadcraft-dwg"); + let tests = dependency("cadcraft-io"); + assert_eq!(tests["kind"], "dev", "the IO crate only inspects acadrust output in tests"); + assert_eq!(tests["target"], bridge["target"], "--all-targets must not add acadrust where the native DWG bridge is unavailable"); + assert_eq!(tests["req"], bridge["req"], "tests must inspect the same decoder version as the bridge"); +} From ec09c87c43cafbc2628da004cc3c201aea021555 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:26:26 +0100 Subject: [PATCH 2/2] Keep DXF regressions enabled without the DWG bridge --- crates/io/tests/dwg_active_layout.rs | 13 ++++++++++--- crates/io/tests/dwg_table_cell_height.rs | 14 +++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/io/tests/dwg_active_layout.rs b/crates/io/tests/dwg_active_layout.rs index fd7d594ed..f2da61dcc 100644 --- a/crates/io/tests/dwg_active_layout.rs +++ b/crates/io/tests/dwg_active_layout.rs @@ -2,13 +2,15 @@ //! the one written as `*Paper_Space` (the DWG header's paper space block), the others //! `*Paper_SpaceN`. Saving used to make the first tab the active one again. -#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] - +// The DWG bridge is unavailable on wasm and FreeBSD ARM64 (#2526); DXF still works. +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] use std::io::Cursor; use cadcraft_doc::{Common, Drawing, EntityKind, Layout, Line, Space}; use cadcraft_geom::Vec3; -use cadcraft_io::{SaveOptions, Version, read, write_with}; +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] +use cadcraft_io::Version; +use cadcraft_io::{SaveOptions, read, write_with}; fn three_sheets() -> Drawing { let mut d = Drawing::new_metric(); @@ -21,6 +23,7 @@ fn three_sheets() -> Drawing { } /// The layout the DWG marks active: the one whose block the header names as paper space. +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] fn dwg_active_layout(dwg: &[u8]) -> String { let doc = acadrust::DwgReader::from_stream(Cursor::new(dwg.to_vec())).read().unwrap(); let ps = doc.header.paper_space_block_handle; @@ -33,11 +36,13 @@ fn dwg_active_layout(dwg: &[u8]) -> String { .unwrap() } +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] fn dwg(d: &Drawing) -> Vec { write_with(d, "case.dwg", &SaveOptions { version: Some(Version::R2018), binary: false }).unwrap() } #[test] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] fn the_active_layout_stays_active_through_dwg_saves() { let mut d = three_sheets(); d.active_layout = Some("S2".into()); @@ -70,6 +75,7 @@ fn the_active_layout_survives_dxf_too() { } #[test] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] fn without_an_active_layout_the_first_tab_is_active() { let mut d = three_sheets(); d.active_layout = Some("gone".into()); @@ -77,6 +83,7 @@ fn without_an_active_layout_the_first_tab_is_active() { } #[test] +#[cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] fn the_active_layout_name_is_matched_ignoring_case() { let mut d = three_sheets(); d.active_layout = Some("s2".into()); diff --git a/crates/io/tests/dwg_table_cell_height.rs b/crates/io/tests/dwg_table_cell_height.rs index 6e077b870..e7f7ca79a 100644 --- a/crates/io/tests/dwg_table_cell_height.rs +++ b/crates/io/tests/dwg_table_cell_height.rs @@ -1,5 +1,7 @@ //! TABLE heights inherit from the table style unless the file records a cell override. -#![cfg(not(any(target_arch = "wasm32", all(target_os = "freebsd", target_arch = "aarch64"))))] +#![cfg(not(target_arch = "wasm32"))] + +// FreeBSD ARM64 has no native DWG bridge (#2526), but the DXF regression still applies. use cadcraft_doc::{Common, Drawing, EntityKind, Space, Table, TableCell, TableStyle}; @@ -42,6 +44,7 @@ fn check(d: &Drawing) { } #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn table_cell_heights_keep_inheritance_through_dwg() { let bytes = cadcraft_io::write(&drawing(), "height.dwg").unwrap(); // A style record can be present for metadata without overriding its text height. @@ -68,11 +71,13 @@ fn table_cell_heights_keep_inheritance_through_dxf() { // ---- cells with their own style record (review on #2588) ---- +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] use acadrust::entities::table::{CellContent, CellStyle, CellStylePropertyFlags}; /// Write the drawing as a DWG, edit its first cell through acadrust (`edit`), write it again as a /// DWG (or, with `as_dxf`, as a DXF by acadrust's writer) and read it with CADCraft. Returns the first cell's /// text height override. +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn first_cell_height(as_dxf: bool, edit: impl FnOnce(&mut acadrust::entities::table::TableCell)) -> Option { let bytes = cadcraft_io::write(&drawing(), "height.dwg").unwrap(); let mut doc = acadrust::DwgReader::from_stream(std::io::Cursor::new(bytes)).read().unwrap(); @@ -100,6 +105,7 @@ fn first_cell_height(as_dxf: bool, edit: impl FnOnce(&mut acadrust::entities::ta .unwrap() } +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn styled(height: f64, flags: CellStylePropertyFlags) -> CellStyle { let mut s = CellStyle::new(); s.text_height = height; @@ -108,12 +114,14 @@ fn styled(height: f64, flags: CellStylePropertyFlags) -> CellStyle { } #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn a_cell_style_with_the_text_height_flag_keeps_its_height() { let h = first_cell_height(false, |c| c.style = Some(styled(0.5, CellStylePropertyFlags::TEXT_HEIGHT))); assert_eq!(h, Some(0.5)); } #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn content_with_format_override_bit_128_keeps_its_own_height() { // acadrust writes the content's height as group 144 and the style's as 140; they differ here // so the result shows which one CADCraft honoured. @@ -131,6 +139,7 @@ fn content_with_format_override_bit_128_keeps_its_own_height() { /// Only the first content's override stands for the cell: a later content's height must not /// replace the first content's inherited one (review on #2588). #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn a_later_contents_height_does_not_replace_the_first_contents() { let h = first_cell_height(false, |c| { c.style = Some(styled(0.18, CellStylePropertyFlags::NONE)); @@ -147,6 +156,7 @@ fn a_later_contents_height_does_not_replace_the_first_contents() { /// Bit 128 with an unusable content height copies nothing, so the writer's default style height /// is still cleared (review on #2588). #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn bit_128_with_an_invalid_height_still_clears_the_default() { for bad in [0.0, -1.0, f64::NAN] { let h = first_cell_height(false, |c| { @@ -162,12 +172,14 @@ fn bit_128_with_an_invalid_height_still_clears_the_default() { } #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn a_cell_with_no_style_record_inherits_the_table_style() { assert_eq!(first_cell_height(false, |c| c.style = None), None); } /// A style record with a height and neither override is not an override, through DWG. #[test] +#[cfg(not(all(target_os = "freebsd", target_arch = "aarch64")))] fn a_style_height_without_an_override_is_cleared() { assert_eq!(first_cell_height(false, |c| c.style = Some(styled(0.18, CellStylePropertyFlags::NONE))), None); }