From 9fc5788b96368ff3ca3f717d1d3b317965c7f789 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 13:33:12 +0200 Subject: [PATCH 01/15] take from python wheel to support arm --- Cargo.toml | 10 ++++++++ build.rs | 71 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e783256..b49826f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,18 @@ categories = ["science"] readme = "README.md" rust-version = "1.70" +[dependencies] +polars = { version = "0.45", optional = true, default-features = false } + [build-dependencies] bindgen = "0.72.0" ureq = "2.0" +zip = "2.2" [features] default = [] gpu = [] +polars-support = ["polars"] [[example]] name = "basic_usage" @@ -24,3 +29,8 @@ path = "examples/basic_usage.rs" [[example]] name = "advanced_usage" path = "examples/advanced_usage.rs" + +[[example]] +name = "polars_usage" +path = "examples/polars_usage.rs" +required-features = ["polars-support"] diff --git a/build.rs b/build.rs index 3f577d3..4330c0d 100644 --- a/build.rs +++ b/build.rs @@ -125,10 +125,64 @@ fn download_lightgbm_headers(out_dir: &Path) -> Result<(), Box Result<(), Box> { - let (os, _arch) = get_platform_info(); + let (os, arch) = get_platform_info(); let version = get_lightgbm_version(); - // LightGBM release binaries (platform-specific) + // Create the library directory + let lib_dir = out_dir.join("libs"); + fs::create_dir_all(&lib_dir)?; + + // For macOS, extract from Python wheel to get architecture-specific binaries + if os == "darwin" { + let wheel_arch = if arch == "aarch64" { "arm64" } else { "x86_64" }; + let wheel_url = format!( + "https://github.com/microsoft/LightGBM/releases/download/v{}/lightgbm-{}-py3-none-macosx_12_0_{}.whl", + version, version, wheel_arch + ); + + println!( + "cargo:warning=Downloading LightGBM v{} macOS {} wheel from: {}", + version, wheel_arch, wheel_url + ); + + // Download the wheel to a temp file + let wheel_path = out_dir.join(format!("lightgbm-{}.whl", wheel_arch)); + let mut dest = fs::File::create(&wheel_path)?; + + let response = ureq::get(&wheel_url).call()?; + let status = response.status(); + if status < 200 || status >= 300 { + return Err(format!("Failed to download wheel: HTTP {}", status).into()); + } + + io::copy(&mut response.into_reader(), &mut dest)?; + drop(dest); // Close file before reading + + // Extract lib_lightgbm.dylib from the wheel + // Wheels are just zip files + let wheel_file = fs::File::open(&wheel_path)?; + let mut archive = zip::ZipArchive::new(wheel_file)?; + + // Find and extract the dylib + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + if file.name().ends_with("lib_lightgbm.dylib") { + let lib_path = lib_dir.join("lib_lightgbm.dylib"); + let mut outfile = fs::File::create(&lib_path)?; + io::copy(&mut file, &mut outfile)?; + + println!( + "cargo:warning=Extracted LightGBM library to: {}", + lib_path.display() + ); + return Ok(()); + } + } + + return Err("lib_lightgbm.dylib not found in wheel".into()); + } + + // For Linux and Windows, use standalone binaries let (lib_filename, download_url) = match os.as_str() { "linux" => ( "lib_lightgbm.so".to_string(), @@ -137,13 +191,6 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box ( - "lib_lightgbm.dylib".to_string(), - format!( - "https://github.com/microsoft/LightGBM/releases/download/v{}/lib_lightgbm.dylib", - version - ), - ), "windows" => ( "lib_lightgbm.dll".to_string(), format!( @@ -159,10 +206,6 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box {} // No rpath needed for Windows } - println!("cargo:rustc-link-lib=dylib=lib_lightgbm"); + println!("cargo:rustc-link-lib=dylib=_lightgbm"); } From d754439197efee4fd9db0e774018053f2d0e2989 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 13:43:26 +0200 Subject: [PATCH 02/15] fix all model pulls. --- build.rs | 173 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/build.rs b/build.rs index 4330c0d..b186197 100644 --- a/build.rs +++ b/build.rs @@ -132,98 +132,115 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box= 300 { - return Err(format!("Failed to download wheel: HTTP {}", status).into()); + // For macOS and Linux, extract from Python wheel to get architecture-specific binaries + match (os.as_str(), arch.as_str()) { + // macOS - both x86_64 and ARM64 available + ("darwin", "aarch64") | ("darwin", "x86_64") => { + let wheel_arch = if arch == "aarch64" { "arm64" } else { "x86_64" }; + let macos_version = if arch == "aarch64" { "12_0" } else { "10_15" }; + let wheel_url = format!( + "https://github.com/microsoft/LightGBM/releases/download/v{}/lightgbm-{}-py3-none-macosx_{}_{}.whl", + version, version, macos_version, wheel_arch + ); + + println!( + "cargo:warning=Downloading LightGBM v{} macOS {} wheel from: {}", + version, wheel_arch, wheel_url + ); + + download_and_extract_from_wheel(&wheel_url, out_dir, &lib_dir, "lib_lightgbm.dylib")?; } - io::copy(&mut response.into_reader(), &mut dest)?; - drop(dest); // Close file before reading - - // Extract lib_lightgbm.dylib from the wheel - // Wheels are just zip files - let wheel_file = fs::File::open(&wheel_path)?; - let mut archive = zip::ZipArchive::new(wheel_file)?; - - // Find and extract the dylib - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - if file.name().ends_with("lib_lightgbm.dylib") { - let lib_path = lib_dir.join("lib_lightgbm.dylib"); - let mut outfile = fs::File::create(&lib_path)?; - io::copy(&mut file, &mut outfile)?; - - println!( - "cargo:warning=Extracted LightGBM library to: {}", - lib_path.display() - ); - return Ok(()); - } + // Linux - both x86_64 and ARM64 available + ("linux", "aarch64") | ("linux", "x86_64") => { + let (wheel_platform, lib_pattern) = if arch == "aarch64" { + ("manylinux2014_aarch64", "lib_lightgbm.so") + } else { + ("manylinux_2_28_x86_64", "lib_lightgbm.so") + }; + + let wheel_url = format!( + "https://github.com/microsoft/LightGBM/releases/download/v{}/lightgbm-{}-py3-none-{}.whl", + version, version, wheel_platform + ); + + println!( + "cargo:warning=Downloading LightGBM v{} Linux {} wheel from: {}", + version, arch, wheel_url + ); + + download_and_extract_from_wheel(&wheel_url, out_dir, &lib_dir, lib_pattern)?; } - return Err("lib_lightgbm.dylib not found in wheel".into()); - } + // Windows - only x86_64 available (no ARM64 support yet) + ("windows", "x86_64") | ("windows", "i686") => { + // For Windows, extract from wheel as well for consistency + let wheel_url = format!( + "https://github.com/microsoft/LightGBM/releases/download/v{}/lightgbm-{}-py3-none-win_amd64.whl", + version, version + ); - // For Linux and Windows, use standalone binaries - let (lib_filename, download_url) = match os.as_str() { - "linux" => ( - "lib_lightgbm.so".to_string(), - format!( - "https://github.com/microsoft/LightGBM/releases/download/v{}/lib_lightgbm.so", - version - ), - ), - "windows" => ( - "lib_lightgbm.dll".to_string(), - format!( - "https://github.com/microsoft/LightGBM/releases/download/v{}/lib_lightgbm.dll", - version - ), - ), - _ => return Err(format!("Unsupported platform: {}", os).into()), - }; + println!( + "cargo:warning=Downloading LightGBM v{} Windows x86_64 wheel from: {}", + version, wheel_url + ); - println!( - "cargo:warning=Downloading LightGBM v{} library from: {}", - version, download_url - ); + download_and_extract_from_wheel(&wheel_url, out_dir, &lib_dir, "lib_lightgbm.dll")?; + } + + ("windows", "aarch64") => { + return Err("Windows ARM64 is not currently supported by LightGBM releases. Please use x86_64 Windows or compile LightGBM from source.".into()); + } + + _ => { + return Err(format!("Unsupported platform/architecture combination: {} / {}", os, arch).into()); + } + } - // Download the library directly into the `libs` directory with its correct name - let lib_path = lib_dir.join(&lib_filename); - let mut dest = fs::File::create(&lib_path)?; + Ok(()) +} - let response = ureq::get(&download_url).call()?; +fn download_and_extract_from_wheel( + wheel_url: &str, + out_dir: &Path, + lib_dir: &Path, + lib_filename: &str, +) -> Result<(), Box> { + // Download the wheel to a temp file + let wheel_path = out_dir.join("lightgbm.whl"); + let mut dest = fs::File::create(&wheel_path)?; + + let response = ureq::get(wheel_url).call()?; let status = response.status(); if status < 200 || status >= 300 { - return Err(format!("Failed to download library: HTTP {}", status).into()); + return Err(format!("Failed to download wheel: HTTP {}", status).into()); } io::copy(&mut response.into_reader(), &mut dest)?; + drop(dest); // Close file before reading + + // Extract the library from the wheel + // Wheels are just zip files + let wheel_file = fs::File::open(&wheel_path)?; + let mut archive = zip::ZipArchive::new(wheel_file)?; + + // Find and extract the library + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + if file.name().ends_with(lib_filename) { + let lib_path = lib_dir.join(lib_filename); + let mut outfile = fs::File::create(&lib_path)?; + io::copy(&mut file, &mut outfile)?; + + println!( + "cargo:warning=Extracted LightGBM library to: {}", + lib_path.display() + ); + return Ok(()); + } + } - println!( - "cargo:warning=Downloaded LightGBM library to: {}", - lib_path.display() - ); - - Ok(()) + Err(format!("{} not found in wheel", lib_filename).into()) } fn main() { From d4e4b76ce0fab76ae3ed91bee59d122df90fae7a Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 13:52:22 +0200 Subject: [PATCH 03/15] add github actions. --- .github/workflows/ci.yml | 221 +++++++++++++++++++++++++++++++++++++++ build.rs | 8 +- 2 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3645902 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,221 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Test ${{ matrix.os }} (${{ matrix.arch }}) - LightGBM ${{ matrix.lightgbm_version }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + # macOS ARM64 (M1/M2/M3) + - os: macos + arch: arm64 + runner: macos-14 + lightgbm_version: "4.6.0" + - os: macos + arch: arm64 + runner: macos-14 + lightgbm_version: "4.5.0" + + # macOS x86_64 (Intel) + - os: macos + arch: x86_64 + runner: macos-13 + lightgbm_version: "4.6.0" + + # Linux x86_64 + - os: linux + arch: x86_64 + runner: ubuntu-latest + lightgbm_version: "4.6.0" + - os: linux + arch: x86_64 + runner: ubuntu-latest + lightgbm_version: "4.5.0" + + # Linux ARM64 + - os: linux + arch: arm64 + runner: ubuntu-24.04-arm64 + lightgbm_version: "4.6.0" + + # Windows x86_64 + - os: windows + arch: x86_64 + runner: windows-latest + lightgbm_version: "4.6.0" + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-${{ matrix.arch }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-cargo-registry- + + - name: Cache cargo index + uses: actions/cache@v4 + with: + path: ~/.cargo/git + key: ${{ runner.os }}-${{ matrix.arch }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-cargo-index- + + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-${{ matrix.arch }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.arch }}-cargo-build-target- + + - name: Install system dependencies (Linux) + if: matrix.os == 'linux' + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + + - name: Check build (no features) + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: cargo check --verbose + + - name: Build (no features) + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: cargo build --verbose + + - name: Run tests (no features) + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: cargo test --verbose + + - name: Check build (with polars-support) + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: cargo check --features polars-support --verbose + + - name: Build (with polars-support) + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: cargo build --features polars-support --verbose + + - name: Run tests (with polars-support) + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: cargo test --features polars-support --verbose + + - name: Build examples + env: + LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} + run: | + cargo build --example basic_usage --verbose + cargo build --example advanced_usage --verbose + cargo build --example polars_usage --features polars-support --verbose + + - name: Verify library architecture (macOS) + if: matrix.os == 'macos' + run: | + echo "Checking library architecture..." + file target/debug/lib_lightgbm.dylib + lipo -info target/debug/lib_lightgbm.dylib + + - name: Verify library architecture (Linux) + if: matrix.os == 'linux' + run: | + echo "Checking library architecture..." + file target/debug/lib_lightgbm.so + readelf -h target/debug/lib_lightgbm.so | grep Machine + + - name: Verify library exists (Windows) + if: matrix.os == 'windows' + run: | + echo "Checking library exists..." + Get-Item target/debug/lib_lightgbm.dll + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Run clippy (no features) + run: cargo clippy -- -D warnings + + - name: Run clippy (with polars-support) + run: cargo clippy --features polars-support -- -D warnings + + fmt: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Check formatting + run: cargo fmt -- --check + + # Test minimum supported LightGBM version + min-version: + name: Test minimum LightGBM version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Test with LightGBM 4.0.0 + env: + LIGHTGBM_VERSION: "4.0.0" + run: | + cargo check --verbose + cargo build --verbose + + # Test unsupported platform (should fail gracefully) + unsupported-platform: + name: Test Windows ARM64 (unsupported) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Add ARM64 target + run: rustup target add aarch64-pc-windows-msvc + + - name: Test unsupported platform detection + continue-on-error: true + run: cargo build --target aarch64-pc-windows-msvc --verbose + id: unsupported_build + + - name: Verify build failed with correct error + if: steps.unsupported_build.outcome == 'failure' + run: echo "Build failed as expected for Windows ARM64" diff --git a/build.rs b/build.rs index b186197..38a6baa 100644 --- a/build.rs +++ b/build.rs @@ -172,8 +172,8 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box { + // Windows - only x86_64 available + ("windows", "x86_64") => { // For Windows, extract from wheel as well for consistency let wheel_url = format!( "https://github.com/microsoft/LightGBM/releases/download/v{}/lightgbm-{}-py3-none-win_amd64.whl", @@ -188,6 +188,10 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box { + return Err("Windows 32-bit (i686) is not supported by LightGBM releases. Please use x86_64 Windows or compile LightGBM from source.".into()); + } + ("windows", "aarch64") => { return Err("Windows ARM64 is not currently supported by LightGBM releases. Please use x86_64 Windows or compile LightGBM from source.".into()); } From 5501fb57b2fcbae9dc60586c6138945129cf0fb4 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 13:52:37 +0200 Subject: [PATCH 04/15] add release action. --- .github/workflows/release.yml | 118 ++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..01e6042 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,118 @@ +name: Release + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + # Comprehensive pre-release testing across all platforms + pre-release-test: + name: Pre-release test ${{ matrix.os }} (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - os: macos + arch: arm64 + runner: macos-14 + - os: macos + arch: x86_64 + runner: macos-13 + - os: linux + arch: x86_64 + runner: ubuntu-latest + - os: linux + arch: arm64 + runner: ubuntu-24.04-arm64 + - os: windows + arch: x86_64 + runner: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install system dependencies (Linux) + if: matrix.os == 'linux' + run: | + sudo apt-get update + sudo apt-get install -y libclang-dev + + # Test with latest LightGBM version + - name: Build and test (latest LightGBM) + run: | + cargo build --release --verbose + cargo test --release --verbose + cargo build --release --features polars-support --verbose + cargo test --release --features polars-support --verbose + + # Build all examples + - name: Build examples + run: | + cargo build --release --example basic_usage + cargo build --release --example advanced_usage + cargo build --release --example polars_usage --features polars-support + + # Archive release artifacts + - name: Archive artifacts (Unix) + if: matrix.os != 'windows' + run: | + mkdir -p artifacts + cp target/release/lib_lightgbm.* artifacts/ || true + tar -czf lightgbm-rust-${{ matrix.os }}-${{ matrix.arch }}.tar.gz artifacts/ + + - name: Archive artifacts (Windows) + if: matrix.os == 'windows' + run: | + New-Item -ItemType Directory -Force -Path artifacts + Copy-Item target/release/lib_lightgbm.* artifacts/ -ErrorAction SilentlyContinue + Compress-Archive -Path artifacts/* -DestinationPath lightgbm-rust-${{ matrix.os }}-${{ matrix.arch }}.zip + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: lightgbm-rust-${{ matrix.os }}-${{ matrix.arch }} + path: | + lightgbm-rust-${{ matrix.os }}-${{ matrix.arch }}.* + + # Upload artifacts to the release + upload-release-artifacts: + name: Upload Release Artifacts + needs: pre-release-test + runs-on: ubuntu-latest + if: github.event_name == 'release' + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Upload artifacts to release + uses: softprops/action-gh-release@v1 + with: + files: artifacts/**/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Publish to crates.io + publish: + name: Publish to crates.io + needs: pre-release-test + runs-on: ubuntu-latest + if: github.event_name == 'release' && !github.event.release.prerelease + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Publish to crates.io + run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} From 1fe33cb90a58834b772b4a38cb2e4585f0ba3090 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 14:13:24 +0200 Subject: [PATCH 05/15] try fixing ci. --- .github/workflows/ci.yml | 10 +++++--- build.rs | 51 +++++++++++++++++++++++++++----------- examples/advanced_usage.rs | 13 +++++----- examples/basic_usage.rs | 11 ++++---- src/error.rs | 2 +- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3645902..ff890a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: # macOS x86_64 (Intel) - os: macos arch: x86_64 - runner: macos-13 + runner: macos-15-large lightgbm_version: "4.6.0" # Linux x86_64 @@ -44,10 +44,9 @@ jobs: runner: ubuntu-latest lightgbm_version: "4.5.0" - # Linux ARM64 - os: linux arch: arm64 - runner: ubuntu-24.04-arm64 + runner: ubuntu-latest lightgbm_version: "4.6.0" # Windows x86_64 @@ -92,6 +91,11 @@ jobs: sudo apt-get update sudo apt-get install -y libclang-dev + - name: Install system dependencies (macOS) + if: matrix.os == 'macos' + run: | + brew install libomp + - name: Check build (no features) env: LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} diff --git a/build.rs b/build.rs index 38a6baa..f3b9d02 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,9 @@ extern crate bindgen; use std::env; -use std::path::{Path, PathBuf}; use std::fs; use std::io; +use std::path::{Path, PathBuf}; fn get_lightgbm_version() -> String { env::var("LIGHTGBM_VERSION").unwrap_or_else(|_| "4.6.0".to_string()) @@ -87,7 +87,10 @@ fn download_lightgbm_headers(out_dir: &Path) -> Result<(), Box= 200 && response.status() < 300 => { @@ -102,7 +105,10 @@ fn download_lightgbm_headers(out_dir: &Path) -> Result<(), Box= 200 && resp.status() < 300 => { @@ -117,7 +123,9 @@ fn download_lightgbm_headers(out_dir: &Path) -> Result<(), Box { - println!("cargo:warning=arrow.h not available for this version (optional, only in v4.2.0+)"); + println!( + "cargo:warning=arrow.h not available for this version (optional, only in v4.2.0+)" + ); } } @@ -197,7 +205,11 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box { - return Err(format!("Unsupported platform/architecture combination: {} / {}", os, arch).into()); + return Err(format!( + "Unsupported platform/architecture combination: {} / {}", + os, arch + ) + .into()); } } @@ -267,7 +279,7 @@ fn main() { .header("wrapper.h") .clang_arg(format!("-I{}", lgbm_include_root.display())) .clang_arg("-xc++") - .clang_arg("-std=c++11") + .clang_arg("-std=c++14") // Only generate bindings for functions starting with LGBM_ .allowlist_function("LGBM_.*") // Allowlist the main types we need @@ -317,8 +329,7 @@ fn main() { .join(env::var("PROFILE").unwrap()); let lib_dest_path = target_dir.join(lib_filename); - fs::copy(&lib_source_path, &lib_dest_path) - .expect("Failed to copy library to target directory"); + fs::copy(&lib_source_path, &lib_dest_path).expect("Failed to copy library to target directory"); // Set the library search path for the build-time linker let lib_search_path = out_dir.join("libs"); @@ -333,19 +344,31 @@ fn main() { // For macOS, add multiple rpath entries for IDE compatibility println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../.."); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_search_path.display()); + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{}", + lib_search_path.display() + ); // Add the target directory to rpath as well if let Some(target_root) = out_dir.ancestors().find(|p| p.ends_with("target")) { - println!("cargo:rustc-link-arg=-Wl,-rpath,{}/debug", target_root.display()); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}/release", target_root.display()); + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{}/debug", + target_root.display() + ); + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{}/release", + target_root.display() + ); } - }, + } "linux" => { // For Linux, use $ORIGIN println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN"); println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN/../.."); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_search_path.display()); - }, + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{}", + lib_search_path.display() + ); + } _ => {} // No rpath needed for Windows } diff --git a/examples/advanced_usage.rs b/examples/advanced_usage.rs index 6c2b70d..2638e08 100644 --- a/examples/advanced_usage.rs +++ b/examples/advanced_usage.rs @@ -1,4 +1,4 @@ -use lightgbm_rust::{Booster, predict_type}; +use lightgbm_rust::{predict_type, Booster}; fn main() -> Result<(), Box> { // Load a trained LightGBM model @@ -16,10 +16,7 @@ fn main() -> Result<(), Box> { println!(" Classes: {}", num_classes); // Example data with f32 (more memory efficient for large datasets) - let data_f32: Vec = vec![ - 1.0, 2.0, 3.0, 4.0, 5.0, - 2.0, 3.0, 4.0, 5.0, 6.0, - ]; + let data_f32: Vec = vec![1.0, 2.0, 3.0, 4.0, 5.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let num_rows = 2; let num_cols = 5; @@ -32,11 +29,13 @@ fn main() -> Result<(), Box> { println!("Raw scores: {:?}", raw_scores); println!("\n--- Leaf Index Prediction ---"); - let leaf_indices = booster.predict_f32(&data_f32, num_rows, num_cols, predict_type::LEAF_INDEX)?; + let leaf_indices = + booster.predict_f32(&data_f32, num_rows, num_cols, predict_type::LEAF_INDEX)?; println!("Leaf indices: {:?}", leaf_indices); println!("\n--- Feature Contribution (SHAP) ---"); - let contributions = booster.predict_f32(&data_f32, num_rows, num_cols, predict_type::CONTRIB)?; + let contributions = + booster.predict_f32(&data_f32, num_rows, num_cols, predict_type::CONTRIB)?; println!("Feature contributions: {:?}", contributions); Ok(()) diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs index f4c3037..5cc36f8 100644 --- a/examples/basic_usage.rs +++ b/examples/basic_usage.rs @@ -1,4 +1,4 @@ -use lightgbm_rust::{Booster, predict_type}; +use lightgbm_rust::{predict_type, Booster}; fn main() -> Result<(), Box> { // Load a trained LightGBM model @@ -29,15 +29,16 @@ fn main() -> Result<(), Box> { // Example: Predict for multiple samples (batch prediction) let batch_data = vec![ - 1.0, 2.0, 3.0, 4.0, // Sample 1 - 2.0, 3.0, 4.0, 5.0, // Sample 2 - 3.0, 4.0, 5.0, 6.0, // Sample 3 + 1.0, 2.0, 3.0, 4.0, // Sample 1 + 2.0, 3.0, 4.0, 5.0, // Sample 2 + 3.0, 4.0, 5.0, 6.0, // Sample 3 ]; let num_rows = 3; let num_cols = 4; println!("\nMaking batch prediction..."); - let batch_predictions = booster.predict(&batch_data, num_rows, num_cols, predict_type::NORMAL)?; + let batch_predictions = + booster.predict(&batch_data, num_rows, num_cols, predict_type::NORMAL)?; println!("Batch predictions: {:?}", batch_predictions); diff --git a/src/error.rs b/src/error.rs index 37793a2..3e78ca8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ +use crate::sys; use std::ffi::CStr; use std::fmt; -use crate::sys; pub type LightGBMResult = std::result::Result; From 5d7b5f335a0460d3c1891c742533c6e157aa1d2f Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 14:18:27 +0200 Subject: [PATCH 06/15] remove polars checks for now. --- .github/workflows/ci.yml | 50 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff890a0..82112dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,28 +111,12 @@ jobs: LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} run: cargo test --verbose - - name: Check build (with polars-support) - env: - LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} - run: cargo check --features polars-support --verbose - - - name: Build (with polars-support) - env: - LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} - run: cargo build --features polars-support --verbose - - - name: Run tests (with polars-support) - env: - LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} - run: cargo test --features polars-support --verbose - - name: Build examples env: LIGHTGBM_VERSION: ${{ matrix.lightgbm_version }} run: | cargo build --example basic_usage --verbose cargo build --example advanced_usage --verbose - cargo build --example polars_usage --features polars-support --verbose - name: Verify library architecture (macOS) if: matrix.os == 'macos' @@ -168,9 +152,6 @@ jobs: - name: Run clippy (no features) run: cargo clippy -- -D warnings - - name: Run clippy (with polars-support) - run: cargo clippy --features polars-support -- -D warnings - fmt: name: Format runs-on: ubuntu-latest @@ -203,23 +184,24 @@ jobs: cargo build --verbose # Test unsupported platform (should fail gracefully) + # Commented out because it requires manual verification unsupported-platform: - name: Test Windows ARM64 (unsupported) - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 + name: Test Windows ARM64 (unsupported) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable + - name: Install Rust + uses: dtolnay/rust-toolchain@stable - - name: Add ARM64 target - run: rustup target add aarch64-pc-windows-msvc + - name: Add ARM64 target + run: rustup target add aarch64-pc-windows-msvc - - name: Test unsupported platform detection - continue-on-error: true - run: cargo build --target aarch64-pc-windows-msvc --verbose - id: unsupported_build + - name: Test unsupported platform detection + continue-on-error: true + run: cargo build --target aarch64-pc-windows-msvc --verbose + id: unsupported_build - - name: Verify build failed with correct error - if: steps.unsupported_build.outcome == 'failure' - run: echo "Build failed as expected for Windows ARM64" + - name: Verify build failed with correct error + if: steps.unsupported_build.outcome == 'failure' + run: echo "Build failed as expected for Windows ARM64" From 13c6ac3dcb6e6d0992d09d0be5e88b00e0adaafe Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 15:38:15 +0200 Subject: [PATCH 07/15] fix linter. --- build.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build.rs b/build.rs index f3b9d02..e38706b 100644 --- a/build.rs +++ b/build.rs @@ -54,7 +54,7 @@ fn download_lightgbm_headers(out_dir: &Path) -> Result<(), Box= 300 { + if !(200..300).contains(&status) { return Err(format!("Failed to download c_api.h: HTTP {}", status).into()); } @@ -72,7 +72,7 @@ fn download_lightgbm_headers(out_dir: &Path) -> Result<(), Box= 300 { + if !(200..300).contains(&status) { return Err(format!("Failed to download export.h: HTTP {}", status).into()); } @@ -228,7 +228,7 @@ fn download_and_extract_from_wheel( let response = ureq::get(wheel_url).call()?; let status = response.status(); - if status < 200 || status >= 300 { + if !(200..300).contains(&status) { return Err(format!("Failed to download wheel: HTTP {}", status).into()); } @@ -300,7 +300,8 @@ fn main() { .blocklist_type(".*_Tp.*") .blocklist_type(".*_Pred.*") .size_t_is_usize(true) - .rustfmt_bindings(true) + // Allow dead code since we don't use all FFI functions + .raw_line("#![allow(dead_code)]") .generate() .expect("Unable to generate bindings."); From e731bf2abd46e2a548d54c8e6ef13637854b1974 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 15:40:27 +0200 Subject: [PATCH 08/15] fix linter dead code. --- build.rs | 2 -- src/sys.rs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/build.rs b/build.rs index e38706b..785b045 100644 --- a/build.rs +++ b/build.rs @@ -300,8 +300,6 @@ fn main() { .blocklist_type(".*_Tp.*") .blocklist_type(".*_Pred.*") .size_t_is_usize(true) - // Allow dead code since we don't use all FFI functions - .raw_line("#![allow(dead_code)]") .generate() .expect("Unable to generate bindings."); diff --git a/src/sys.rs b/src/sys.rs index a38a13a..cd503e4 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -1,5 +1,6 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); From 942af3bde84762c4bd13d2b98fe94170f0300541 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Sun, 9 Nov 2025 15:45:51 +0200 Subject: [PATCH 09/15] change release to accept inputs. --- .github/workflows/release.yml | 99 +++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01e6042..babf4c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,12 +4,100 @@ on: release: types: [published] workflow_dispatch: + inputs: + release_type: + description: 'Release type' + required: true + type: choice + options: + - patch + - minor + - major jobs: + # Create release with version bump + create-release: + name: Create Release + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Bump version + id: version + run: | + # Get current version from Cargo.toml + CURRENT_VERSION=$(grep "^version" Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') + echo "Current version: $CURRENT_VERSION" + + # Split version into major.minor.patch + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + + # Bump version based on input + case "${{ github.event.inputs.release_type }}" in + major) + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + ;; + minor) + MINOR=$((MINOR + 1)) + PATCH=0 + ;; + patch) + PATCH=$((PATCH + 1)) + ;; + esac + + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + echo "New version: $NEW_VERSION" + + # Update Cargo.toml + sed -i "s/^version = \".*\"/version = \"$NEW_VERSION\"/" Cargo.toml + + # Output for later steps + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Commit version bump + run: | + git add Cargo.toml + git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" + git push origin ${{ github.ref_name }} + + - name: Create tag + run: | + git tag ${{ steps.version.outputs.tag }} + git push origin ${{ steps.version.outputs.tag }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.tag }} + name: Release ${{ steps.version.outputs.version }} + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Comprehensive pre-release testing across all platforms pre-release-test: name: Pre-release test ${{ matrix.os }} (${{ matrix.arch }}) runs-on: ${{ matrix.runner }} + needs: [create-release] + if: always() && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') strategy: fail-fast: false matrix: @@ -82,9 +170,9 @@ jobs: # Upload artifacts to the release upload-release-artifacts: name: Upload Release Artifacts - needs: pre-release-test + needs: [create-release, pre-release-test] runs-on: ubuntu-latest - if: github.event_name == 'release' + if: always() && needs.pre-release-test.result == 'success' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') steps: - uses: actions/checkout@v4 @@ -96,6 +184,7 @@ jobs: - name: Upload artifacts to release uses: softprops/action-gh-release@v1 with: + tag_name: ${{ github.event_name == 'workflow_dispatch' && needs.create-release.outputs.tag || github.event.release.tag_name }} files: artifacts/**/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -103,11 +192,13 @@ jobs: # Publish to crates.io publish: name: Publish to crates.io - needs: pre-release-test + needs: [create-release, pre-release-test] runs-on: ubuntu-latest - if: github.event_name == 'release' && !github.event.release.prerelease + if: always() && needs.pre-release-test.result == 'success' && ((github.event_name == 'release' && !github.event.release.prerelease) || github.event_name == 'workflow_dispatch') steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && needs.create-release.outputs.tag || github.ref }} - name: Install Rust uses: dtolnay/rust-toolchain@stable From 444aadef2ea7e262cd0f5c0e3e919abeee687f24 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Mon, 10 Nov 2025 09:12:18 +0200 Subject: [PATCH 10/15] fix formating and remove polars support for now. --- .github/workflows/release.yml | 3 --- Cargo.toml | 7 ------- src/model.rs | 38 ++++++++++++++++++----------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index babf4c1..497b12b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,15 +135,12 @@ jobs: run: | cargo build --release --verbose cargo test --release --verbose - cargo build --release --features polars-support --verbose - cargo test --release --features polars-support --verbose # Build all examples - name: Build examples run: | cargo build --release --example basic_usage cargo build --release --example advanced_usage - cargo build --release --example polars_usage --features polars-support # Archive release artifacts - name: Archive artifacts (Unix) diff --git a/Cargo.toml b/Cargo.toml index b49826f..6ca22de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ readme = "README.md" rust-version = "1.70" [dependencies] -polars = { version = "0.45", optional = true, default-features = false } [build-dependencies] bindgen = "0.72.0" @@ -20,7 +19,6 @@ zip = "2.2" [features] default = [] gpu = [] -polars-support = ["polars"] [[example]] name = "basic_usage" @@ -29,8 +27,3 @@ path = "examples/basic_usage.rs" [[example]] name = "advanced_usage" path = "examples/advanced_usage.rs" - -[[example]] -name = "polars_usage" -path = "examples/polars_usage.rs" -required-features = ["polars-support"] diff --git a/src/model.rs b/src/model.rs index 4df0943..20ff678 100644 --- a/src/model.rs +++ b/src/model.rs @@ -49,14 +49,12 @@ pub struct Booster { impl Booster { /// Load a model from a file pub fn load>(path: P) -> LightGBMResult { - let path_str = path.as_ref().to_str() - .ok_or_else(|| LightGBMError { - description: "Path contains invalid UTF-8 characters".to_string(), - })?; - let path_c_str = CString::new(path_str) - .map_err(|e| LightGBMError { - description: format!("Path contains NUL byte: {}", e), - })?; + let path_str = path.as_ref().to_str().ok_or_else(|| LightGBMError { + description: "Path contains invalid UTF-8 characters".to_string(), + })?; + let path_c_str = CString::new(path_str).map_err(|e| LightGBMError { + description: format!("Path contains NUL byte: {}", e), + })?; let mut handle: sys::BoosterHandle = ptr::null_mut(); let mut num_iterations = 0i32; @@ -85,10 +83,9 @@ impl Booster { /// let booster = Booster::load_from_string(&model_string).unwrap(); /// ``` pub fn load_from_string(model_str: &str) -> LightGBMResult { - let model_c_str = CString::new(model_str) - .map_err(|e| LightGBMError { - description: format!("Model string contains NUL byte: {}", e), - })?; + let model_c_str = CString::new(model_str).map_err(|e| LightGBMError { + description: format!("Model string contains NUL byte: {}", e), + })?; let mut handle: sys::BoosterHandle = ptr::null_mut(); let mut num_iterations = 0i32; @@ -118,10 +115,9 @@ impl Booster { /// ``` pub fn load_from_buffer(buffer: &[u8]) -> LightGBMResult { // Convert bytes to string (LightGBM models are text-based) - let model_str = std::str::from_utf8(buffer) - .map_err(|e| LightGBMError { - description: format!("Invalid UTF-8 in model buffer: {}", e), - })?; + let model_str = std::str::from_utf8(buffer).map_err(|e| LightGBMError { + description: format!("Invalid UTF-8 in model buffer: {}", e), + })?; Self::load_from_string(model_str) } @@ -173,7 +169,10 @@ impl Booster { return Err(LightGBMError { description: format!( "Input data size mismatch: expected {} elements ({}×{}), got {}", - expected_len, num_rows, num_cols, data.len() + expected_len, + num_rows, + num_cols, + data.len() ), }); } @@ -243,7 +242,10 @@ impl Booster { return Err(LightGBMError { description: format!( "Input data size mismatch: expected {} elements ({}×{}), got {}", - expected_len, num_rows, num_cols, data.len() + expected_len, + num_rows, + num_cols, + data.len() ), }); } From 31a766319f6c9c08fa688b83eff631bbec4c50cb Mon Sep 17 00:00:00 2001 From: aryehlev Date: Mon, 10 Nov 2025 19:59:20 +0200 Subject: [PATCH 11/15] fix windows linking. --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 785b045..1164f32 100644 --- a/build.rs +++ b/build.rs @@ -371,5 +371,5 @@ fn main() { _ => {} // No rpath needed for Windows } - println!("cargo:rustc-link-lib=dylib=_lightgbm"); + println!("cargo:rustc-link-lib=dylib=lib_lightgbm"); } From 001db5560953aaef4c01f7ef55d6b7910cbb3956 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Mon, 10 Nov 2025 20:29:18 +0200 Subject: [PATCH 12/15] fix windows and put back regular linking. --- build.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/build.rs b/build.rs index 1164f32..185e99a 100644 --- a/build.rs +++ b/build.rs @@ -182,7 +182,7 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box { - // For Windows, extract from wheel as well for consistency + // For Windows, extract from wheel - need both DLL and import library let wheel_url = format!( "https://github.com/microsoft/LightGBM/releases/download/v{}/lightgbm-{}-py3-none-win_amd64.whl", version, version @@ -193,7 +193,7 @@ fn download_compiled_library(out_dir: &Path) -> Result<(), Box { @@ -259,6 +259,72 @@ fn download_and_extract_from_wheel( Err(format!("{} not found in wheel", lib_filename).into()) } +fn download_and_extract_windows_libs( + wheel_url: &str, + out_dir: &Path, + lib_dir: &Path, +) -> Result<(), Box> { + // Download the wheel to a temp file + let wheel_path = out_dir.join("lightgbm.whl"); + let mut dest = fs::File::create(&wheel_path)?; + + let response = ureq::get(wheel_url).call()?; + let status = response.status(); + if !(200..300).contains(&status) { + return Err(format!("Failed to download wheel: HTTP {}", status).into()); + } + + io::copy(&mut response.into_reader(), &mut dest)?; + drop(dest); // Close file before reading + + // Extract both the DLL and the import library from the wheel + // Wheels are just zip files + let wheel_file = fs::File::open(&wheel_path)?; + let mut archive = zip::ZipArchive::new(wheel_file)?; + + let mut dll_found = false; + let mut lib_found = false; + + // Find and extract both lib_lightgbm.dll and lib_lightgbm.lib + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let filename = file.name(); + + if filename.ends_with("lib_lightgbm.dll") { + let lib_path = lib_dir.join("lib_lightgbm.dll"); + let mut outfile = fs::File::create(&lib_path)?; + io::copy(&mut file, &mut outfile)?; + println!( + "cargo:warning=Extracted LightGBM DLL to: {}", + lib_path.display() + ); + dll_found = true; + } else if filename.ends_with("lib_lightgbm.lib") { + let lib_path = lib_dir.join("lib_lightgbm.lib"); + let mut outfile = fs::File::create(&lib_path)?; + io::copy(&mut file, &mut outfile)?; + println!( + "cargo:warning=Extracted LightGBM import library to: {}", + lib_path.display() + ); + lib_found = true; + } + + if dll_found && lib_found { + return Ok(()); + } + } + + if !dll_found { + return Err("lib_lightgbm.dll not found in wheel".into()); + } + if !lib_found { + return Err("lib_lightgbm.lib not found in wheel".into()); + } + + Ok(()) +} + fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let lgbm_include_root = out_dir.join("include"); @@ -330,6 +396,15 @@ fn main() { let lib_dest_path = target_dir.join(lib_filename); fs::copy(&lib_source_path, &lib_dest_path).expect("Failed to copy library to target directory"); + // On Windows, also copy the import library (.lib) to the libs directory for linking + if os == "windows" { + let import_lib_source = out_dir.join("libs").join("lib_lightgbm.lib"); + if import_lib_source.exists() { + // No need to copy the .lib to target dir, it's only used during linking + println!("cargo:warning=Found import library at: {}", import_lib_source.display()); + } + } + // Set the library search path for the build-time linker let lib_search_path = out_dir.join("libs"); println!( @@ -368,8 +443,13 @@ fn main() { lib_search_path.display() ); } - _ => {} // No rpath needed for Windows + "windows" => { + // On Windows, we need to tell the linker where to find the DLL at runtime + // Copy the DLL to the output directory (already done above) + println!("cargo:rustc-link-lib=dylib=lib_lightgbm"); + } + _ => { + println!("cargo:rustc-link-lib=dylib=_lightgbm"); + } } - - println!("cargo:rustc-link-lib=dylib=lib_lightgbm"); } From 0184876b6a9ce0fe8df7591093d0b66f0a28876f Mon Sep 17 00:00:00 2001 From: aryehlev Date: Mon, 10 Nov 2025 21:46:26 +0200 Subject: [PATCH 13/15] put back failed. --- .github/workflows/release.yml | 4 ++-- build.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 497b12b..1d7812a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,6 @@ name: Release on: - release: - types: [published] workflow_dispatch: inputs: release_type: @@ -13,6 +11,8 @@ on: - patch - minor - major + release: + types: [published] jobs: # Create release with version bump diff --git a/build.rs b/build.rs index 185e99a..0b8a60b 100644 --- a/build.rs +++ b/build.rs @@ -433,6 +433,7 @@ fn main() { target_root.display() ); } + println!("cargo:rustc-link-lib=dylib=_lightgbm"); } "linux" => { // For Linux, use $ORIGIN @@ -442,14 +443,13 @@ fn main() { "cargo:rustc-link-arg=-Wl,-rpath,{}", lib_search_path.display() ); + println!("cargo:rustc-link-lib=dylib=_lightgbm"); } "windows" => { // On Windows, we need to tell the linker where to find the DLL at runtime // Copy the DLL to the output directory (already done above) println!("cargo:rustc-link-lib=dylib=lib_lightgbm"); } - _ => { - println!("cargo:rustc-link-lib=dylib=_lightgbm"); - } + _ => {} } } From 77933b106221afdf3c4ea9225bfebc583ab097f0 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Mon, 10 Nov 2025 21:50:27 +0200 Subject: [PATCH 14/15] format build.rs --- build.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 0b8a60b..9ae7dab 100644 --- a/build.rs +++ b/build.rs @@ -401,7 +401,10 @@ fn main() { let import_lib_source = out_dir.join("libs").join("lib_lightgbm.lib"); if import_lib_source.exists() { // No need to copy the .lib to target dir, it's only used during linking - println!("cargo:warning=Found import library at: {}", import_lib_source.display()); + println!( + "cargo:warning=Found import library at: {}", + import_lib_source.display() + ); } } From b012a8a57efb93f6c4532a7fb8b18c57085b7ad3 Mon Sep 17 00:00:00 2001 From: aryehlev Date: Mon, 10 Nov 2025 21:54:56 +0200 Subject: [PATCH 15/15] change to c++17. --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 9ae7dab..82b3df7 100644 --- a/build.rs +++ b/build.rs @@ -345,7 +345,7 @@ fn main() { .header("wrapper.h") .clang_arg(format!("-I{}", lgbm_include_root.display())) .clang_arg("-xc++") - .clang_arg("-std=c++14") + .clang_arg("-std=c++17") // Only generate bindings for functions starting with LGBM_ .allowlist_function("LGBM_.*") // Allowlist the main types we need