diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4bdafac..be728ab 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -47,3 +47,22 @@ jobs: cd .. cargo clean cargo build --no-default-features --features "discover" + + wasm-emscripten: + runs-on: ubuntu-latest + env: + EMSDK_VERSION: 5.0.0 + EMSDK_CACHE_FOLDER: emsdk-cache + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Setup emsdk + uses: pyodide/setup-emsdk@v15 + with: + version: ${{ env.EMSDK_VERSION }} + actions-cache-folder: ${{ env.EMSDK_CACHE_FOLDER }} + - name: Build wasm32-unknown-emscripten + run: | + rustup target add wasm32-unknown-emscripten + cargo build --target wasm32-unknown-emscripten diff --git a/build.rs b/build.rs index 677988e..ac8f558 100644 --- a/build.rs +++ b/build.rs @@ -33,6 +33,9 @@ fn generate_bindings<'a>( exclude_dir: Option, include_paths: impl IntoIterator, ) { + let target = env::var("TARGET").unwrap(); + let emscripten = target.contains("emscripten"); + // The bindgen::Builder is the main entry point // to bindgen, and lets you build up options for // the resulting bindings. @@ -41,6 +44,22 @@ fn generate_bindings<'a>( .fold(bindgen::Builder::default(), |builder, path| { builder.clang_arg(format!("-I{}", path.to_string_lossy())) }); + + // Work around bindgen/libclang missing `Highs_*` function declarations for + // `wasm32-unknown-emscripten` when parsing with emscripten's sysroot. + // The C API signatures are target-independent, so parse as host and skip + // cross-target layout assertions. + let builder = if emscripten { + println!("cargo:rerun-if-env-changed=HOST"); + let host = env::var("HOST") + .expect("TARGET is emscripten but HOST is not set; needed for bindgen host parsing"); + builder + .clang_arg(format!("--target={host}")) + .layout_tests(false) + } else { + builder + }; + let c_bindings = builder // The input header we would like to generate bindings for. // This is a trivial wrapper header so that the HiGHS headers @@ -64,12 +83,24 @@ fn generate_bindings<'a>( #[cfg(feature = "build")] fn build() -> bool { use cmake::Config; + let target = env::var("TARGET").unwrap(); + let emscripten = target.contains("emscripten"); let mut dst = Config::new("HiGHS"); if cfg!(feature = "ninja") { dst.generator("Ninja"); } + dst.define("BUILD_CXX_EXE", "OFF"); + dst.define("BUILD_EXAMPLES", "OFF"); + + // `cmake` crate default C++ flags inject `-fno-exceptions` for this target, + // but HiGHS requires C++ exceptions. Use explicit flags for emscripten. + if emscripten { + dst.no_default_flags(true); + dst.cxxflag("-fexceptions"); + } + // Avoid using downstream project's profile setting for HiGHS build. if cfg!(feature = "highs_release") { dst.profile("Release"); @@ -94,11 +125,10 @@ fn build() -> bool { println!("cargo:rustc-link-lib=z"); } - let target = env::var("TARGET").unwrap(); let apple = target.contains("apple"); let linux = target.contains("linux"); let mingw = target.contains("pc-windows-gnu"); - if apple { + if apple || emscripten { println!("cargo:rustc-link-lib=c++"); } else if linux || mingw { println!("cargo:rustc-link-lib=stdc++");