From 750434e235b1d58445f90b0dc83385bc2ae9ff36 Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Thu, 13 Nov 2025 07:42:43 +0100 Subject: [PATCH] Rust: Vendor C sources for cargo publish --- .github/workflows/rust.yml | 8 ++++ .gitignore | 1 + rust/Cargo.toml | 8 ++++ rust/Makefile | 15 +++++++- rust/README.md | 13 ++++++- rust/build.rs | 76 +++++++++++++++++++++++++++----------- 6 files changed, 98 insertions(+), 23 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 114bacda1..52c7b70fa 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -66,3 +66,11 @@ jobs: - name: Run Rust tests run: make test working-directory: rust + + - name: Vendor C sources for packaging + run: make vendor + working-directory: rust + + - name: Test cargo package + run: cargo package --allow-dirty + working-directory: rust diff --git a/.gitignore b/.gitignore index dbb3303e3..bc36d9b6b 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,7 @@ java/.java_compiled # Rust Build Artifacts rust/target/ +rust/vendor/ rust/Cargo.lock # NX Monorepo diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9595f31fa..53a1b4112 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -9,6 +9,14 @@ license = "MIT" repository = "https://github.com/marcoroth/herb" keywords = ["erb", "html", "html+erb", "ruby", "rails"] categories = ["parser-implementations", "development-tools"] +include = [ + "src/**/*", + "vendor/**/*.c", + "vendor/**/*.h", + "build.rs", + "Cargo.toml", + "README.md" +] [lib] name = "herb" diff --git a/rust/Makefile b/rust/Makefile index 83f6802ef..e462a8613 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -5,7 +5,7 @@ BIN_NAME = herb-rust BIN_PATH = $(BUILD_DIR)/debug/$(BIN_NAME) RELEASE_BIN_PATH = $(BUILD_DIR)/release/$(BIN_NAME) -.PHONY: all build release clean test cli help templates format +.PHONY: all build release clean test cli help templates format vendor all: templates build @@ -44,6 +44,18 @@ clean: cargo clean @echo "Cleaned Rust build artifacts" +vendor: + @echo "Vendoring C sources into vendor/libherb..." + rm -rf vendor/ + mkdir -p vendor/libherb/ + mkdir -p vendor/prism/ + cp -r ../src vendor/libherb/ + @echo "Vendoring prism library..." + PRISM_PATH=$$(bundle show prism) && \ + cp -r "$$PRISM_PATH/src" vendor/prism/ && \ + cp -r "$$PRISM_PATH/include" vendor/prism/ + @echo "Vendored C sources and prism successfully" + help: @echo "Herb Rust Build System" @echo "" @@ -55,6 +67,7 @@ help: @echo " cli - Show CLI usage" @echo " test - Run Rust tests" @echo " format - Format Rust code with rustfmt" + @echo " vendor - Vendor C sources into vendor/" @echo " clean - Remove build artifacts" @echo " help - Show this help" @echo "" diff --git a/rust/README.md b/rust/README.md index 35b4e8870..ffdc9af68 100644 --- a/rust/README.md +++ b/rust/README.md @@ -19,7 +19,7 @@ make all # Generate templates and build ## Usage -### CLI +### CLI (within the Herb repo) ```bash ./bin/herb-rust version @@ -55,6 +55,17 @@ fn main() { cargo test ``` +## Publishing + +Before publishing to crates.io, vendor the C sources: + +```bash +make vendor # Vendor C sources from ../src and prism +cargo publish --allow-dirty # Publish to crates.io +``` + +The `vendor/` directory is gitignored to avoid committing duplicate files. The `make vendor` task copies C sources from the parent directory into `vendor/libherb` and `vendor/prism` so the published crate is self-contained. + ## Cleaning ```bash diff --git a/rust/build.rs b/rust/build.rs index cd414ad01..844c4847e 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -4,14 +4,51 @@ use std::process::Command; fn main() { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let root_dir = manifest_dir.parent().unwrap(); - let src_dir = root_dir.join("src"); - let prism_path = get_prism_path(root_dir); - let prism_include = prism_path.join("include"); - let prism_build = prism_path.join("build"); + let vendor_src_dir = manifest_dir.join("vendor/libherb/src"); + let vendor_include_dir = manifest_dir.join("vendor/libherb/src/include"); - println!("cargo:rustc-link-search=native={}", prism_build.display()); - println!("cargo:rustc-link-lib=static=prism"); + let (src_dir, include_dir, root_dir) = if vendor_src_dir.exists() { + (vendor_src_dir, vendor_include_dir, manifest_dir.clone()) + } else { + let root = manifest_dir.parent().unwrap(); + ( + root.join("src"), + root.join("src/include"), + root.to_path_buf(), + ) + }; + + let vendor_prism_src = manifest_dir.join("vendor/prism/src"); + let vendor_prism_include = manifest_dir.join("vendor/prism/include"); + + let prism_include = if vendor_prism_src.exists() { + let mut prism_sources = Vec::new(); + for path in glob::glob(vendor_prism_src.join("**/*.c").to_str().unwrap()) + .unwrap() + .flatten() + { + prism_sources.push(path); + } + + let mut prism_build = cc::Build::new(); + prism_build + .flag("-std=c99") + .flag("-fPIC") + .opt_level(2) + .include(&vendor_prism_include) + .files(&prism_sources) + .warnings(false); + + prism_build.compile("prism"); + + vendor_prism_include + } else { + let prism_path = get_prism_path(&root_dir); + let prism_build = prism_path.join("build"); + println!("cargo:rustc-link-search=native={}", prism_build.display()); + println!("cargo:rustc-link-lib=static=prism"); + prism_path.join("include") + }; let mut c_sources = Vec::new(); @@ -32,22 +69,22 @@ fn main() { .flag("-Wno-unused-parameter") .flag("-fPIC") .opt_level(2) - .include(src_dir.join("include")) + .include(&include_dir) .include(&prism_include) .files(&c_sources); build.compile("herb"); let bindings = bindgen::Builder::default() - .header(src_dir.join("include/analyze.h").to_str().unwrap()) - .header(src_dir.join("include/herb.h").to_str().unwrap()) - .header(src_dir.join("include/ast_nodes.h").to_str().unwrap()) - .header(src_dir.join("include/errors.h").to_str().unwrap()) - .header(src_dir.join("include/element_source.h").to_str().unwrap()) - .header(src_dir.join("include/token_struct.h").to_str().unwrap()) - .header(src_dir.join("include/util/hb_string.h").to_str().unwrap()) - .header(src_dir.join("include/util/hb_array.h").to_str().unwrap()) - .clang_arg(format!("-I{}", src_dir.join("include").display())) + .header(include_dir.join("analyze.h").to_str().unwrap()) + .header(include_dir.join("herb.h").to_str().unwrap()) + .header(include_dir.join("ast_nodes.h").to_str().unwrap()) + .header(include_dir.join("errors.h").to_str().unwrap()) + .header(include_dir.join("element_source.h").to_str().unwrap()) + .header(include_dir.join("token_struct.h").to_str().unwrap()) + .header(include_dir.join("util/hb_string.h").to_str().unwrap()) + .header(include_dir.join("util/hb_array.h").to_str().unwrap()) + .clang_arg(format!("-I{}", include_dir.display())) .clang_arg(format!("-I{}", prism_include.display())) .allowlist_function("herb_.*") .allowlist_function("hb_array_.*") @@ -86,10 +123,7 @@ fn main() { println!("cargo:rerun-if-changed={}", source.display()); } - println!( - "cargo:rerun-if-changed={}", - src_dir.join("include").display() - ); + println!("cargo:rerun-if-changed={}", include_dir.display()); println!("cargo:rerun-if-changed=build.rs"); }