use std::path::{Path, PathBuf};
const LIBREDWG_SOURCES: &[&str] = &[
"bits.c",
"classes.c",
"codepages.c",
"common.c",
"decode.c",
"decode2.c",
"decode_r11.c",
"decode_r2007.c",
"dwg.c",
"dwg_api.c",
"dxfclasses.c",
"dynapi.c",
"encode.c",
"encode2.c",
"free.c",
"geom.c",
"hash.c",
"in_dxf.c",
"logging.c",
"objects.c",
"out_dxf.c",
"out_dxfb.c",
"print.c",
"reedsolomon.c",
];
const LOCAL_PATCH_MARKER: &[u8] = b"uncad local patch";
const LOCAL_PATCHES: &[(&str, usize)] = &[
("src/acds.spec", 2),
("src/common.c", 3),
("src/common_entity_data.spec", 3),
("src/decode.c", 2),
("src/dwg.spec", 1),
];
fn main() {
let pointer_width =
std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH").expect("cargo sets this for build scripts");
assert_eq!(
pointer_width, "64",
"libredwg-sys only supports 64-bit targets: vendor-config/config.h hardcodes \
SIZEOF_SIZE_T=8 (and other 64-bit assumptions) with no 32-bit branch, unvalidated \
and unsafe to silently use on a {pointer_width}-bit target. See docs/CAVEATS.md."
);
check_libclang();
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let libredwg_src = manifest_dir.join("vendor/libredwg/src");
let libredwg_include = manifest_dir.join("vendor/libredwg/include");
let vendor_config = manifest_dir.join("vendor-config");
let shim_dir = manifest_dir.join("shim");
let actual_c_files: Vec<String> = std::fs::read_dir(&libredwg_src)
.expect("crates/libredwg-sys/vendor/libredwg/src should exist (checked into git -- see scripts/sync-libredwg-vendor.sh)")
.filter_map(|e| e.ok())
.filter_map(|e| e.file_name().into_string().ok())
.filter(|n| n.ends_with(".c"))
.collect();
if actual_c_files.len() != LIBREDWG_SOURCES.len() {
panic!(
"vendor/libredwg/src/*.c file count changed ({} found, {} expected). The vendored \
copy was likely edited or re-synced with a different file set -- re-check the \
LIBREDWG_SOURCES list in build.rs against vendor/libredwg/src before proceeding \
(see docs/ARCHITECTURE.md).",
actual_c_files.len(),
LIBREDWG_SOURCES.len()
);
}
let vendor_root = libredwg_src
.parent()
.expect("vendor/libredwg/src always has a parent directory");
let found_patches = local_patch_markers(vendor_root);
let expected_patches: Vec<(String, usize)> = LOCAL_PATCHES
.iter()
.map(|&(path, count)| (path.to_string(), count))
.collect();
if found_patches != expected_patches {
panic!(
"vendor/libredwg carries these `uncad local patch` markers (file, count): \
{found_patches:?}, but build.rs expects {expected_patches:?}. \
scripts/sync-libredwg-vendor.sh deletes the local patches on every run: re-apply \
them (docs/CAVEATS.md, \"Local patches to the vendored LibreDWG\"), or, if one \
was dropped or added on purpose, update LOCAL_PATCHES in build.rs, NOTICE.md and \
docs/CAVEATS.md together."
);
}
if let Some(repo_root) = manifest_dir.parent().and_then(|p| p.parent()) {
let submodule_src = repo_root.join("lib/libredwg/src");
if let Ok(entries) = std::fs::read_dir(&submodule_src) {
const EXCLUDED_JSON_SOURCES: &[&str] = &["in_json.c", "out_json.c", "out_geojson.c"];
let submodule_c_count = entries
.filter_map(|e| e.ok())
.filter_map(|e| e.file_name().into_string().ok())
.filter(|n| n.ends_with(".c"))
.count();
let expected_submodule_total = LIBREDWG_SOURCES.len() + EXCLUDED_JSON_SOURCES.len();
if submodule_c_count != expected_submodule_total {
println!(
"cargo:warning=lib/libredwg/src/*.c ({submodule_c_count} files) has drifted \
from vendor/libredwg/src ({expected_submodule_total} expected: {} compiled + \
{} excluded JSON). Upstream LibreDWG was likely updated -- re-run \
scripts/sync-libredwg-vendor.sh and update LIBREDWG_SOURCES in build.rs.",
LIBREDWG_SOURCES.len(),
EXCLUDED_JSON_SOURCES.len()
);
}
}
}
let mut build = cc::Build::new();
build
.include(&vendor_config)
.include(&libredwg_src)
.include(&libredwg_include)
.warnings(false);
if build.get_compiler().is_like_msvc() {
build.flag("/utf-8").flag("/std:c11");
}
if build.get_compiler().is_like_msvc() {
for warning in ["/we4013", "/we4047", "/we4022", "/we4024", "/we4133"] {
build.flag(warning);
}
} else {
for warning in [
"-Werror=implicit-function-declaration",
"-Werror=int-conversion",
"-Werror=incompatible-pointer-types",
] {
build.flag_if_supported(warning);
}
}
let target = std::env::var("TARGET").expect("cargo sets TARGET for build scripts");
let bindings = bindgen::Builder::default()
.header(shim_dir.join("wrapper.h").to_string_lossy())
.clang_arg(format!("--target={target}"))
.clang_arg("-xc")
.clang_args(msvc_system_include_args(&build))
.clang_arg("-std=c11")
.clang_arg(format!("-I{}", vendor_config.display()))
.clang_arg(format!("-I{}", libredwg_src.display()))
.clang_arg(format!("-I{}", libredwg_include.display()))
.clang_arg(format!("-I{}", shim_dir.display()))
.allowlist_function("dwg_read_file")
.allowlist_function("dwg_get_num_objects")
.allowlist_function("dwg_get_object")
.allowlist_function("dwg_object_get_fixedtype")
.allowlist_function("dwg_object_get_dxfname")
.allowlist_function("dwg_object_get_handle")
.allowlist_function("dwg_object_get_index")
.allowlist_function("dwg_object_get_type")
.allowlist_function("get_first_owned_entity")
.allowlist_function("get_next_owned_entity")
.allowlist_function("get_first_owned_subentity")
.allowlist_function("get_next_owned_subentity")
.allowlist_type("dwg_point_3d")
.allowlist_function("uncad_object_entity_ptr")
.allowlist_function("uncad_object_object_ptr")
.allowlist_function("uncad_multileader_get_lines")
.allowlist_function("uncad_multileader_free_lines")
.allowlist_type("uncad_multileader_line_t")
.allowlist_function("uncad_multileader_get_roots")
.allowlist_function("uncad_multileader_free_roots")
.allowlist_type("uncad_multileader_root_t")
.allowlist_function("uncad_multileader_get_content")
.allowlist_type("uncad_multileader_content_t")
.allowlist_function("uncad_entity_style_overrides")
.allowlist_function("uncad_free_style_overrides")
.allowlist_type("uncad_style_override_t")
.allowlist_function("dwg_free")
.allowlist_function("uncad_dwg_is_pre_r13")
.allowlist_function("uncad_table_entry_index")
.allowlist_function("uncad_dwg_is_r13_to_r2000")
.allowlist_function("uncad_dwg_is_r2010_or_later")
.allowlist_function("uncad_dwg_is_r2013_or_later")
.allowlist_function("uncad_dwg_codepage")
.allowlist_function("uncad_dwg_is_wide_string")
.allowlist_function("uncad_dwg_read_bytes")
.allowlist_function("uncad_dwg_version")
.allowlist_function("uncad_dwg_from_version")
.allowlist_function("uncad_dwg_ltype_continuous")
.allowlist_function("uncad_dwg_numheader_vars")
.allowlist_function("uncad_dwg_template_read")
.allowlist_function("dwg_version_type")
.allowlist_function("dwg_version_hdr_type")
.allowlist_type("Dwg_Codepage")
.allowlist_function("dwg_codepage_isasian")
.allowlist_function("dwg_codepage_is_twobyte")
.allowlist_function("dwg_codepage_uc")
.allowlist_function("dwg_codepage_uwc")
.allowlist_function("dwg_codepage_dxfstr")
.allowlist_function("dwg_obj_generic_handlevalue")
.allowlist_function("dwg_next_entity")
.allowlist_function("dwg_resolve_handle")
.allowlist_function("dwg_next_object")
.allowlist_function("dwg_handle_name")
.allowlist_function("dwg_dynapi_.*")
.allowlist_type("Dwg_Data")
.allowlist_type("Dwg_Object")
.allowlist_type("Dwg_Object_Type")
.opaque_type("_dwg_struct")
.opaque_type("Dwg_Data")
.opaque_type("dwg_data") .opaque_type("_dwg_object")
.opaque_type("Dwg_Object")
.opaque_type("dwg_object") .opaque_type("_dwg_object_entity")
.opaque_type("Dwg_Object_Entity")
.opaque_type("_dwg_object_object")
.opaque_type("Dwg_Object_Object")
.opaque_type("_dwg_entity_3DSOLID")
.opaque_type("Dwg_Entity__3DSOLID")
.allowlist_type("DWG_ERROR")
.allowlist_type("Dwg_DYNAPI_field")
.allowlist_type("Dwg_HATCH_PolylinePath")
.blocklist_type("_dwg_HATCH_Path")
.blocklist_type("Dwg_HATCH_Path")
.blocklist_type("_dwg_HATCH_PathSeg")
.blocklist_type("Dwg_HATCH_PathSeg")
.blocklist_type("_dwg_HATCH_ControlPoint")
.blocklist_type("Dwg_HATCH_ControlPoint")
.blocklist_type("_dwg_HATCH_DefLine")
.blocklist_type("Dwg_HATCH_DefLine")
.blocklist_type("_dwg_HATCH_Color")
.blocklist_type("Dwg_HATCH_Color")
.blocklist_type("_dwg_MLINE_vertex")
.blocklist_type("Dwg_MLINE_vertex")
.blocklist_type("_dwg_MLINE_line")
.blocklist_type("Dwg_MLINE_line")
.blocklist_type("_dwg_MLINESTYLE_line")
.blocklist_type("Dwg_MLINESTYLE_line")
.allowlist_type("Dwg_Handle")
.allowlist_type("_dwg_handle")
.allowlist_type("Dwg_Object_Ref")
.allowlist_type("_dwg_object_ref")
.allowlist_type("Dwg_Color")
.allowlist_type("_dwg_color")
.allowlist_type("DWG_COLOR_METHOD")
.allowlist_type("Dwg_Color_Method")
.allowlist_var("DWG_ERR_.*")
.derive_default(true)
.generate_comments(true)
.layout_tests(true)
.generate()
.expect("bindgen should generate libredwg bindings");
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("failed to write bindings.rs");
for src in LIBREDWG_SOURCES {
build.file(libredwg_src.join(src));
}
build.file(shim_dir.join("uncad_shim.c"));
build.compile("libredwg");
println!("cargo:rerun-if-changed={}", shim_dir.display());
println!("cargo:rerun-if-changed={}", vendor_config.display());
println!(
"cargo:rerun-if-changed={}",
libredwg_src
.parent()
.expect("vendor/libredwg/src always has a parent directory")
.display()
);
}
fn local_patch_markers(root: &Path) -> Vec<(String, usize)> {
fn walk(dir: &Path, root: &Path, out: &mut Vec<(String, usize)>) {
let entries =
std::fs::read_dir(dir).unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display()));
for entry in entries {
let path = entry
.unwrap_or_else(|e| panic!("cannot read an entry of {}: {e}", dir.display()))
.path();
if path.is_dir() {
walk(&path, root, out);
continue;
}
let bytes = std::fs::read(&path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let count = bytes
.windows(LOCAL_PATCH_MARKER.len())
.filter(|window| *window == LOCAL_PATCH_MARKER)
.count();
if count > 0 {
let relative = path
.strip_prefix(root)
.expect("the walk stays under its root")
.to_string_lossy()
.replace('\\', "/");
out.push((relative, count));
}
}
}
let mut out = Vec::new();
walk(root, root, &mut out);
out.sort();
out
}
fn check_libclang() {
let previous_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let found = std::panic::catch_unwind(bindgen::clang_version).is_ok();
std::panic::set_hook(previous_hook);
if found {
return;
}
let stale_path = match std::env::var("LIBCLANG_PATH") {
Ok(path) => {
format!("LIBCLANG_PATH is set to '{path}', and no clang library was found there.\n")
}
Err(_) => String::new(),
};
panic!(
"
libredwg-sys generates its FFI bindings with bindgen, which needs libclang,
and no libclang could be found.
{stale_path}
Install LLVM/Clang, then build again:
Windows winget install LLVM.LLVM (or: choco install llvm)
if it is still not found afterwards, set
LIBCLANG_PATH to the directory holding libclang.dll
(a default install puts it in LLVM/bin under
Program Files)
Debian/Ubuntu sudo apt install libclang-dev
Fedora/RHEL sudo dnf install clang-devel
Alpine apk add clang-dev
macOS xcode-select --install, or: brew install llvm
and set LIBCLANG_PATH=$(brew --prefix llvm)/lib
LLVM is needed to build this crate, not to run what it builds.
"
);
}
fn msvc_system_include_args(build: &cc::Build) -> Vec<String> {
let compiler = build.get_compiler();
if !compiler.is_like_msvc() {
return Vec::new();
}
compiler
.env()
.iter()
.filter(|(key, _)| key.eq_ignore_ascii_case("INCLUDE"))
.flat_map(|(_, value)| std::env::split_paths(value).collect::<Vec<_>>())
.filter(|dir| !dir.as_os_str().is_empty())
.map(|dir| format!("-isystem{}", dir.display()))
.collect()
}