diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02e5937..aedcb3d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,109 +1,104 @@ -name: Build Release +name: Release + on: push: tags: - - v* + - 'v*' + +env: + CARGO_TERM_COLOR: always + jobs: - create-release: - runs-on: ubuntu-20.04 - steps: - - name: Create Release - uses: ncipollo/release-action@v1 - with: - allowUpdates: true - omitBody: true - prerelease: true - draft: true - token: ${{ secrets.ACCESS_TOKEN }} build: + name: Build runs-on: ${{ matrix.target.os }} - needs: create-release strategy: - fail-fast: false matrix: target: - - os: ubuntu-20.04 + - os: ubuntu-latest triple: x86_64-unknown-linux-gnu - - os: macos-11 + - os: macos-latest triple: x86_64-apple-darwin - - os: macos-11 - triple: aarch64-apple-darwin - - os: windows-2019 + - os: windows-latest triple: x86_64-pc-windows-msvc - features: ['static-link', 'static-link,nozlib'] + features: [ 'build-assimp', 'static-link' ] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: true - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: Install dependencies (Linux) + + # LLVM comes preinstalled on Windows and macOS runners. + - name: Install LLVM if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install ninja-build - - name: Install LLVM (Windows) # required for bindgen to work, see https://github.com/rust-lang/rust-bindgen/issues/1797 uses: KyleMayes/install-llvm-action@v1 - if: runner.os == 'Windows' with: - version: '11.0' directory: ${{ runner.temp }}/llvm - - name: Install dependencies (macOS) - if: runner.os == 'macOS' + cached: true + version: '14.0' + + # CMake and Rust are preinstalled on all runners. + + - name: Update Rust run: | - brew install ninja - - name: Install Rust target ${{ matrix.target.triple }} - run: | - rustup target add ${{ matrix.target.triple }} - - name: Build ${{ matrix.target.triple }} + rustup default stable + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + + - name: Build + run: cargo build --target ${{ matrix.target.triple }} --features ${{ matrix.features }} + + - name: Package Assimp + run: cargo run --bin package --target ${{ matrix.target.triple }} --bin package --features ${{ matrix.features }} env: - RUSSIMP_BUILD_CACHE_DIR: ${{ runner.temp }}/release - RUSSIMP_BUILD_OUT_DIR: ${{ runner.temp }}/out - RUSSIMP_PREBUILT: OFF - run: | - cargo build -vv --release --no-default-features --features ${{ matrix.features }} --target ${{ matrix.target.triple }} - shell: bash - - name: Upload release - uses: ncipollo/release-action@v1 + RUSSIMP_PACKAGE_DIR: ./russimp-package + + - name: Upload artifacts + uses: actions/upload-artifact@v3 with: - allowUpdates: true - artifacts: ${{ runner.temp }}/release/* - artifactContentType: application/gzip - artifactErrorsFailBuild: true - omitBodyDuringUpdate: true - omitNameDuringUpdate: true - prerelease: true - draft: true - token: ${{ secrets.ACCESS_TOKEN }} - publish-release: - runs-on: ubuntu-20.04 + name: russimp-packages + path: ${{ env.RUSSIMP_PACKAGE_DIR }}/* + env: + RUSSIMP_PACKAGE_DIR: ./russimp-package + + publish: + name: Publish Release needs: build + runs-on: ubuntu-latest steps: - - name: Publish Release - uses: ncipollo/release-action@v1 + - name: Checkout + uses: actions/checkout@v3 with: - allowUpdates: true - omitBody: true - token: ${{ secrets.ACCESS_TOKEN }} - cargo-publish: - needs: publish-release - env: - CRATESIO_TOKEN: ${{ secrets.CRATESIO_TOKEN }} - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Install assimp - run: sudo bash ${GITHUB_WORKSPACE}/install_assimp.bash - - name: Install stable - uses: actions-rs/toolchain@v1 + submodules: true + + - name: Download artifacts + uses: actions/download-artifact@v3 with: - profile: minimal - toolchain: stable - override: true - - name: cargo publish - continue-on-error: true - run: cargo publish --token ${CRATESIO_TOKEN} + name: russimp-packages + path: ${{ runner.temp }}/russimp-package + + - name: List artifacts + run: | + echo "Received artifacts:" + ls -l $RUSSIMP_PACKAGE_DIR + env: + RUSSIMP_PACKAGE_DIR: ${{ runner.temp }}/russimp-package + + - name: Publish release on GitHub + uses: softprops/action-gh-release@v1 + with: + generate_release_notes: true + body: | + This release was automatically created by GitHub Actions. + files: | + ${{ env.RUSSIMP_PACKAGE_DIR }}/* + token: ${{ secrets.ACCESS_TOKEN }} + env: + RUSSIMP_PACKAGE_DIR: ${{ runner.temp }}/russimp-package + + - name: Publish release on crates.io + run: cargo publish --features prebuilt --token $CRATES_IO_TOKEN + env: + CRATES_IO_TOKEN: ${{ secrets.CRATESIO_TOKEN }} + RUSSIMP_PACKAGE_DIR: ${{ runner.temp }}/russimp-package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3299102..7f5039c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,64 +1,58 @@ name: Test -on: [push, pull_request] +on: + push: + branches: + - '**' + tags: + - '!v*' # Exclude tags starting with 'v' + pull_request: env: CARGO_TERM_COLOR: always jobs: - lin-build: - runs-on: ubuntu-latest + test: + name: Test + runs-on: ${{ matrix.target.os }} + strategy: + fail-fast: false + matrix: + target: + - os: ubuntu-latest + triple: x86_64-unknown-linux-gnu + - os: macos-latest + triple: x86_64-apple-darwin + - os: windows-latest + triple: x86_64-pc-windows-msvc + features: [ 'build-assimp', 'static-link' ] steps: - - uses: actions/checkout@v2 - - name: Install assimp - run: sudo bash ${GITHUB_WORKSPACE}/install_assimp.bash - - name: Install stable - uses: actions-rs/toolchain@v1 + - name: Checkout + uses: actions/checkout@v3 with: - profile: minimal - toolchain: stable - override: true - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --lib --verbose + submodules: true - win-build: - runs-on: windows-latest - steps: - - name: Set up Visual Studio shell - uses: egor-tensin/vs-shell@v2 - - name: Set up LIBCLANG_PATH - run: echo "LIBCLANG_PATH=$env:VCINSTALLDIR\Tools\Llvm\x64\bin" >> $env:GITHUB_ENV - - uses: actions/checkout@v2 - - name: Install stable - uses: actions-rs/toolchain@v1 + # LLVM comes preinstalled on Windows and macOS runners + - name: Install LLVM + if: runner.os == 'Linux' + uses: KyleMayes/install-llvm-action@v1 with: - profile: minimal - toolchain: stable - override: true - - name: install cargo-vcpkg - run: cargo install cargo-vcpkg - - name: vcpkg build - run: cargo vcpkg build - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --lib --verbose + directory: ${{ runner.temp }}/llvm + cached: true + version: '14.0' + + # CMake and Rust are preinstalled on all runners. + + - name: Update Rust + run: | + rustup update stable + rustup target add ${{ matrix.target.triple }} + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 - macos-build: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - name: Install assimp - run: brew install assimp - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --lib --verbose + run: cargo build --target ${{ matrix.target.triple }} --features ${{ matrix.features }} + + - name: Test + run: cargo test --target ${{ matrix.target.triple }} --features ${{ matrix.features }} diff --git a/.gitmodules b/.gitmodules index 7d9f53f..c25e11a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "assimp"] path = assimp url = https://github.com/assimp/assimp + branch = "master" diff --git a/Cargo.toml b/Cargo.toml index 1f246ec..9893059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,41 @@ [package] name = "russimp-sys" -version = "1.0.3" +version = "2.0.0" authors = ["Jhonny Knaak de Vargas"] -edition = "2018" +edition = "2021" license-file = "LICENSE" readme = "README.md" homepage = "https://github.com/jkvargas/russimp-sys" categories = ["rendering", "external-ffi-bindings", "game-engines", "multimedia"] keywords = ["assimp", "3d", "blend", "3ds", "glTF"] repository = "https://github.com/jkvargas/russimp-sys" -description = "Raw Assimp bindings for rust" +description = "Raw Assimp bindings for Rust" exclude = ["/assimp", "*.bash", "*.ps1"] [lib] name = "russimp_sys" path = "src/lib.rs" +doctest = false + +[[bin]] +name = "package" +path = "bin/package/main.rs" [features] default = [] -prebuilt = ["static-link"] -static-link = [] -nozlib = [] -nolibcxx = [] - -[build-dependencies] -bindgen = "0.59.2" -vcpkg = "0.2.15" -ureq = "2.5" -serde_json = "1.0" -serde = { version = "1.0", features = ["derive"] } -tar = "0.4" -flate2 = "1.0" -num_cpus = "1.13" +build-assimp = [] +prebuilt = [] +static-link = ["build-assimp"] -[package.metadata.vcpkg] -git = "https://github.com/microsoft/vcpkg" -rev = "7178ff9" +[dependencies] +flate2 = "1.0.25" +tar = "0.4.38" -[package.metadata.vcpkg.target] -x86_64-pc-windows-msvc = { triplet = "x64-windows-static-md", install = ["assimp", "zlib"] } -x86-pc-windows-msvc = { triplet = "x86-windows-static-md", install = ["assimp", "zlib"] } +[build-dependencies] +bindgen = "0.63.0" +built = "0.5.2" +cmake = "0.1.49" +flate2 = "1.0.25" +reqwest = { version = "0.11.13", features = ["blocking", "rustls-tls"] } +tar = "0.4.38" +which = "4.3.0" diff --git a/README.md b/README.md index f5b59a2..32ce576 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,61 @@ # russimp-sys ![russimp-sys](https://github.com/jkvargas/russimp-sys/workflows/russimp-sys/badge.svg?branch=main) [![Crates.io](https://img.shields.io/crates/v/russimp-sys.svg)](https://crates.io/crates/russimp-sys) -Assimp raw bindings for Rust. +Unsafe Rust bindings for the Open Asset Import Library (assimp). +See: [Our safe assimp Rust library](https://github.com/jkvargas/russimp) -There is a high chance that you are actually looking for russimp https://github.com/jkvargas/russimp +Raw bindings for the C API of assimp. -## How to use +## Platform Support +We build, test, and provide prebuilt packages for the following targets: +- x86_64-pc-windows-msvc +- x86_64-apple-darwin +- x86_64-unknown-linux-gnu -**By default**, you will need to have `assimp` installed on your system, if you are an ubuntu user I believe you will need to install `libassimp-dev`. +Additional targets that work when building from source: +- aarch64-apple-darwin (M1 Macs, cross-compiled on x86_64.) +- aarch64-unknown-linux-gnu (Raspberry Pi 4b, built on the machine itself.) -`russimp-sys` will look for `assimp` library and headers from your system, generates rust bindings and [dynamic linking]() to `assimp` shared library. +Platforms that are not supported and won't build: +- x86_64-pc-windows-gnu (See: [assimp/4686]([https://github.com/assimp/assimp/issues/4868)) -**For Windows**, This package uses [cargo-vcpkg](https://crates.io/crates/cargo-vcpkg) to manage system dependencies. Running ```cargo vcpkg build``` will build the necessary dependencies within the target directory. Alternatively provide a VCPKG_ROOT environment variable pointed at the location of a shared vcpkg installation. +## Installation -**For who want a standalone executable file**. Enable [`prebuilt`](#prebuilt) feature, then `russimp-sys` will download the prebuilt `assimp` [static library]() and its dependencies from github, linking libraries to your executable, but it will increase the size of the executable. - -## Features - -You can use the following [FEATURES](https://doc.rust-lang.org/cargo/reference/features.html#the-features-section) to configure the behavior of `russimp-sys`. +**By default** `russimp-sys` is looking for the `assimp` library in the system. +However there are many ways for the crate to install the library for you by specifying these crate features: ### `prebuilt` +This features will download a prebuilt package from this repo's release page, these packages are built and published automatically every time we release a new version. -Download prebuilt `Assimp` static library binaries from github and skip building from source. - -Because `Assimp` build is slow and have build environment requirements. We provide prebuilt binaries for common platforms and features. - -When a new version is released, github action automatically runs pre-build tasks, and all prebuilt binaries are saved in [github releases](https://github.com/jkvargas/russimp-sys/releases). - -The `russimp-sys` build script will try to download the prebuilt binaries from github first, and skip the full source build. +In addition, you can specify a local package by setting the `RUSSIMP_PACKAGE_DIR` environment variable to the path of the package. +You can run the provided package binary to generate a package for your platform. -### `static-link` +```cargo run --bin package --features ``` -Enabling `static-link` feature without `prebuilt` feature, will build `assimp` from source. +### `build-assimp` or `static-link` +The `build-assimp` feature will build the library from source and link it dynamically. +The `static-link` feature will build the library from source and link it statically. -Build from source need the following dependencies: +Building from source requires the following dependencies: +- CMake +- libclang (for `bindgen`) +- A C/C++ compiler +- RECOMMENDED: Ninja (For Windows users the buildscript automatically uses Ninja if it finds it in the PATH) -* cmake -* clang -* Ninja for Linux and MacOS, Visual Studio 2019 for Windows +### Additional Features: ### `nozlib` -By default `russimp-sys` will statically link zlibstatic, you can disable this feature if it conflicts with other dependencies. - -### `nolibcxx` - -By default `russimp-sys` links to `libstdc++` in linux and `libc++` in macos, turning this on `russimp-sys` won't link to the c++ standard library. +By default `russimp-sys` will statically link `zlibstatic`. Enabling this feature will link to the system's `zlib` library. ## Changelog - +### 2.0.0 +* Complete overhaul of the build process. + +* Expose all assimp headers. +* Rework CI pipeline. +* Support for local assimp packaging and local package usage. (See: `prebuilt` feature) +* Remove vcpkg support. +* Remove `nolibcxx` feature. ### 1.0.3 * Builds based on 5.2.5 release ### 1.0.0 - -- Builds based on 5.1.0 release diff --git a/assimp b/assimp index 02f0706..67eae8e 160000 --- a/assimp +++ b/assimp @@ -1 +1 @@ -Subproject commit 02f070667a142b6a54058d85af8a852a20625c78 +Subproject commit 67eae8ee5afa149b11267de8ec87de1538fa80b6 diff --git a/bin/package/main.rs b/bin/package/main.rs new file mode 100644 index 0000000..2710237 --- /dev/null +++ b/bin/package/main.rs @@ -0,0 +1,78 @@ +use russimp_sys::*; + +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs::{self, File}; +use std::io; +use std::path::PathBuf; + +const LICENSE_FILEPATH: &str = "LICENSE"; + +const fn static_lib() -> &'static str { + if cfg!(feature = "build-assimp") && cfg!(not(feature = "static-link")) { + "dylib" + } else if cfg!(feature = "static-link") { + "static" + } else { + "" + } +} + +fn main() -> Result<(), Box> { + if static_lib().is_empty() { + return Err(Box::new(io::Error::new( + io::ErrorKind::Other, + "You must specify either the `static-link` or `build-assimp` feature", + ))); + } + + let out_dir = PathBuf::from(env!("OUT_DIR")); + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let ar_dst_dir = PathBuf::from(option_env!("RUSSIMP_PACKAGE_DIR").unwrap_or(env!("OUT_DIR"))); + + let target = russimp_sys::built_info::TARGET; + let ar_filename = format!( + "russimp-{}-{}-{}.tar.gz", + env!("CARGO_PKG_VERSION"), + target, + static_lib() + ); + + let from_dir = out_dir.join(static_lib()); + let mut licence = File::open(manifest_dir.join(LICENSE_FILEPATH))?; + + fs::create_dir_all(&ar_dst_dir)?; + println!("Packaging at: {}", ar_dst_dir.display()); + + let tar_file = File::create(ar_dst_dir.join(&ar_filename))?; + let mut archive = tar::Builder::new(GzEncoder::new(tar_file, Compression::best())); + + // On Windows, the dynamic libraries are located in the bin directory. + if static_lib() == "dylib" && cfg!(target_env = "msvc") { + archive.append_dir_all(format!("bin"), from_dir.join("bin"))?; + } + + archive.append_dir_all("include", from_dir.join("include"))?; + archive.append_dir_all(format!("lib"), from_dir.join("lib"))?; + archive.append_file(format!("{}", LICENSE_FILEPATH), &mut licence)?; + + archive.finish()?; + + let (major, minor, patch) = unsafe { + ( + aiGetVersionMajor(), + aiGetVersionMinor(), + aiGetVersionPatch(), + ) + }; + + println!( + "Package created at: {}\nAssimp version: {}.{}.{}", + ar_dst_dir.join(&ar_filename).display(), + major, + minor, + patch, + ); + + Ok(()) +} diff --git a/build.rs b/build.rs index 658bb66..a952f4d 100644 --- a/build.rs +++ b/build.rs @@ -1,417 +1,198 @@ -mod build_support; - -use std::{ - env, fs, io, - path::{Path, PathBuf}, - process::Command, - time::SystemTime, -}; - -use build_support::{static_lib_filename, Target}; -use flate2::{read::GzDecoder, write::GzEncoder, Compression}; -use serde::{Deserialize, Serialize}; - -const BINDINGS_FILE: &str = "bindings.rs"; -const WRAPPER_FILE: &str = "wrapper.h"; - -fn run_bindgen(output_file: impl AsRef, include_path: Option<&Path>) -> Result<(), ()> { - let mut builder = bindgen::Builder::default(); - if let Some(include_path) = include_path { - builder = builder.clang_arg(format!("-I{}", include_path.to_str().unwrap())); +use flate2::read::GzDecoder; +use std::{env, fs, io, path::PathBuf}; + +struct Library(&'static str, &'static str); + +const fn static_lib() -> &'static str { + if cfg!(feature = "static-link") { + "static" + } else { + "dylib" } - builder - .header(WRAPPER_FILE) - .parse_callbacks(Box::new(bindgen::CargoCallbacks)) - .allowlist_type("ai.*") - .allowlist_function("ai.*") - .allowlist_var("ai.*") - .allowlist_var("AI_.*") - .derive_partialeq(true) - .derive_eq(true) - .derive_hash(true) - .derive_debug(true) - .generate()? - .write_to_file(output_file.as_ref()) - .unwrap(); - Ok(()) } -#[derive(Serialize, Deserialize, Debug, Clone)] -struct BuildManifest { - pub link_search_dir: PathBuf, - pub assimp_license: PathBuf, - pub link_libs: Vec, - pub bindings_rs: PathBuf, - pub target: String, +const fn build_zlib() -> bool { + cfg!(not(feature = "nozlib")) } -fn install(manifest: &BuildManifest) { - println!( - "cargo:rustc-link-search=native={}", - manifest.link_search_dir.display().to_string() - ); +// Compiler specific compiler flags for CMake +fn compiler_flags() -> Vec<&'static str> { + let mut flags = Vec::new(); - for lib in &manifest.link_libs { - println!("cargo:rustc-link-lib=static={}", lib); - } - - let target = Target::parse_target(&manifest.target); + if cfg!(target_env = "msvc") { + flags.push("/EHsc"); - if target.system == "linux" && cfg!(not(feature = "nolibcxx")) { - println!("cargo:rustc-link-lib={}", "stdc++"); - } - - if target.system == "darwin" && cfg!(not(feature = "nolibcxx")) { - println!("cargo:rustc-link-lib={}", "c++"); + // Find Ninja + if which::which("ninja").is_ok() { + env::set_var("CMAKE_GENERATOR", "Ninja"); + } } - // Write the bindings to the /bindings.rs file. - let bindings_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join(BINDINGS_FILE); - fs::copy(&manifest.bindings_rs, bindings_path).unwrap(); + flags } -fn build_from_source(target: &Target) -> BuildManifest { - let current_dir = env::current_dir().expect("Failed to get current dir"); +fn lib_names() -> Vec { + let mut names = Vec::new(); - println!("cargo:rerun-if-env-changed=ASSIMP_SOURCE_DIR"); - // use or /assimp - let assimp_source_dir = current_dir.join( - env::var("ASSIMP_SOURCE_DIR") - .unwrap_or(current_dir.join("assimp").to_str().unwrap().to_string()), - ); - if assimp_source_dir.exists() == false { - // source dir not exist, try to clone it - let mut git_clone = Command::new("git"); - git_clone - .arg("clone") - .arg("https://github.com/assimp/assimp.git") - .arg(&assimp_source_dir) - .arg("--depth=1"); - build_support::run_command(&mut git_clone, "git"); - } - - println!("cargo:rerun-if-env-changed=RUSSIMP_BUILD_OUT_DIR"); - // use or /build-from-source - let out_dir = current_dir.join( - env::var("RUSSIMP_BUILD_OUT_DIR").unwrap_or( - PathBuf::from(env::var("OUT_DIR").unwrap()) - .join("build-from-source") - .to_string_lossy() - .to_string(), - ), - ); - - let assimp_build_dir = out_dir.join("assimp"); - let assimp_install_dir = assimp_build_dir.join("out"); - let assimp_lib_dir = assimp_install_dir.join("lib"); - let assimp_include_dir = assimp_install_dir.join("include"); - let assimp_license = assimp_source_dir.join("LICENSE"); - let bindings_rs = out_dir.join("bindings.rs"); - - // configure assimp - fs::create_dir_all(&assimp_build_dir).unwrap(); - let mut assimp_cmake = Command::new("cmake"); - assimp_cmake - .current_dir(&assimp_build_dir) - .arg(&assimp_source_dir) - .arg(format!("-DCMAKE_BUILD_TYPE={}", "Release")) - .arg(format!( - "-DCMAKE_INSTALL_PREFIX={}", - assimp_install_dir.to_str().unwrap() - )) - .arg(format!("-DBUILD_SHARED_LIBS={}", "OFF")) - .arg(format!("-DASSIMP_BUILD_ASSIMP_TOOLS={}", "OFF")) - .arg(format!("-DASSIMP_BUILD_TESTS={}", "OFF")) - .arg(format!( - "-DASSIMP_BUILD_ZLIB={}", - if cfg!(feature = "nozlib") { - "OFF" - } else { - "ON" - } - )); - - if target.system == "windows" { - // if windows - if target.abi == Some("gnu".to_owned()) { - panic!("MinGW is not supported"); - } - - match target.architecture.as_str() { - "x86_64" => assimp_cmake.args(["-A", "x64"]), - "i686" => assimp_cmake.args(["-A", "Win32"]), - _ => panic!("Unsupported architecture"), - }; + if cfg!(target_os = "windows") && cfg!(target_env = "gnu") { + panic!("Windows GNU is not supported, assimp fails to build for some reason.\nSee https://github.com/assimp/assimp/issues/4868"); } else { - // if not windows, use ninja and clang - assimp_cmake - .env( - "CMAKE_GENERATOR", - env::var("CMAKE_GENERATOR").unwrap_or("Ninja".to_string()), - ) - .env("CC", env::var("CC").unwrap_or("clang".to_string())) - .env("CXX", env::var("CXX").unwrap_or("clang++".to_string())) - .env("ASM", env::var("ASM").unwrap_or("clang".to_string())) - .env( - "CXXFLAGS", - env::var("CXXFLAGS").unwrap_or(format!("-target {}", target.to_string())), - ) - .env( - "CFLAGS", - env::var("CFLAGS").unwrap_or(format!("-target {}", target.to_string())), - ); + names.push(Library("assimp", static_lib())); } - build_support::run_command(&mut assimp_cmake, "cmake"); - - // build assimp - let mut assimp_cmake_install = Command::new("cmake"); - assimp_cmake_install - .current_dir(&assimp_build_dir) - .args(["--build", "."]) - .args(["--target", "install"]) - .args(["--config", "Release"]) - .args([ - "--parallel", - &env::var("NUM_JOBS").unwrap_or(num_cpus::get().to_string()), - ]); - - build_support::run_command(&mut assimp_cmake_install, "cmake"); - - let mut link_libs = if target.system == "windows" { - // if windows, there is a suffix after the assimp library name, find library name here. - let assimp_lib = fs::read_dir(&assimp_lib_dir) - .unwrap() - .map(|e| e.unwrap()) - .find(|f| f.file_name().to_string_lossy().starts_with("assimp")) - .expect("Failed to find assimp library"); - vec![assimp_lib - .file_name() - .to_str() - .unwrap() - .split('.') - .next() - .unwrap() - .to_owned()] + if build_zlib() { + names.push(Library("zlibstatic", "static")); } else { - vec!["assimp".to_owned()] - }; - - if cfg!(not(feature = "nozlib")) { - link_libs.push("zlibstatic".to_owned()) + names.push(Library("z", "dylib")); } - // generate bindings.rs - run_bindgen(&bindings_rs, Some(&assimp_include_dir)).unwrap(); + if cfg!(target_os = "linux") { + names.push(Library("stdc++", "dylib")); + } - BuildManifest { - link_search_dir: assimp_lib_dir, - assimp_license, - link_libs, - bindings_rs, - target: target.to_string(), + if cfg!(target_os = "macos") { + names.push(Library("c++", "dylib")); } + + names } -fn package(manifest: &BuildManifest, output: impl AsRef) { - let file = fs::File::create(output).unwrap(); - let enc = GzEncoder::new(file, Compression::default()); - let mut tar_builder = tar::Builder::new(enc); +fn build_from_source() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - tar_builder - .append_file( - "bindings.rs", - &mut fs::File::open(&manifest.bindings_rs).unwrap(), - ) - .unwrap(); - tar_builder - .append_file( - "LICENSE", - &mut fs::File::open(&manifest.assimp_license).unwrap(), - ) - .unwrap(); + // Build Zlib from source? + let build_zlib = if build_zlib() { "ON" } else { "OFF" }; - tar_builder - .append_dir("lib", &manifest.link_search_dir) - .unwrap(); + // Build static libs? + let build_shared = if static_lib() == "static" { + "OFF" + } else { + "ON" + }; - for lib_name in &manifest.link_libs { - let filename = static_lib_filename(&lib_name); - tar_builder - .append_file( - format!("lib/{}", filename), - &mut fs::File::open(&manifest.link_search_dir.join(filename)).unwrap(), - ) - .unwrap(); + // CMake + let mut cmake = cmake::Config::new("assimp"); + cmake + .profile("Release") + .static_crt(true) + .out_dir(out_dir.join(static_lib())) + .define("BUILD_SHARED_LIBS", build_shared) + .define("ASSIMP_BUILD_ASSIMP_TOOLS", "OFF") + .define("ASSIMP_BUILD_TESTS", "OFF") + .define("ASSIMP_BUILD_ZLIB", build_zlib) + .define("LIBRARY_SUFFIX", ""); + + // Add compiler flags + for flag in compiler_flags().iter() { + cmake.cflag(flag); + cmake.cxxflag(flag); } - let manifest_json = serde_json::to_string(&BuildManifest { - link_search_dir: PathBuf::from("lib"), - assimp_license: PathBuf::from("LICENSE"), - link_libs: manifest.link_libs.clone(), - bindings_rs: PathBuf::from("bindings.rs"), - target: manifest.target.clone(), - }) - .unwrap(); - let manifest_json_date = manifest_json.as_bytes(); - let mut header = tar::Header::new_gnu(); - header.set_size(manifest_json_date.len() as u64); - header.set_cksum(); - header.set_mode(0o644); - header.set_mtime( - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as u64, - ); + let cmake_dir = cmake.build(); - tar_builder - .append_data(&mut header, "manifest.json", manifest_json_date) - .unwrap(); - - tar_builder.finish().unwrap(); -} - -fn download_from_cache(cache_tar_name: impl AsRef, version: impl AsRef) -> BuildManifest { - let download_url = format!( - "https://github.com/jkvargas/russimp-sys/releases/download/v{}/{}", - version.as_ref(), - cache_tar_name.as_ref() + println!( + "cargo:rustc-link-search=native={}", + cmake_dir.join("lib").display() ); - println!("Downloading {}", download_url); - let package = build_support::download(cache_tar_name, download_url).expect("Download Failed"); - return unpack(&package); + println!( + "cargo:rustc-link-search=native={}", + cmake_dir.join("bin").display() + ); } -fn unpack(package: impl AsRef) -> BuildManifest { - let unpack_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("unpack"); - fs::create_dir_all(&unpack_dir).unwrap(); - - let file = fs::File::open(package).unwrap(); - let mut tar_archive = tar::Archive::new(GzDecoder::new(file)); +fn link_from_package() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let target = env::var("TARGET").unwrap(); + let crate_version = env::var("CARGO_PKG_VERSION").unwrap(); + let archive_name = format!( + "russimp-{}-{}-{}.tar.gz", + crate_version, + target, + static_lib() + ); - tar_archive.unpack(&unpack_dir).unwrap(); + let ar_src_dir; - let manifest_json = unpack_dir.join("manifest.json"); - let manifest: BuildManifest = - serde_json::from_reader(io::BufReader::new(fs::File::open(manifest_json).unwrap())) - .unwrap(); + if option_env!("RUSSIMP_PACKAGE_DIR").is_some() { + ar_src_dir = PathBuf::from(env::var("RUSSIMP_PACKAGE_DIR").unwrap()); + } else { + ar_src_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let dl_link = format!( + "https://github.com/jkvargas/russimp-sys/releases/download/v{}/{}", + crate_version, archive_name + ); - BuildManifest { - link_search_dir: unpack_dir.join(manifest.link_search_dir), - assimp_license: unpack_dir.join(manifest.assimp_license), - link_libs: manifest.link_libs.clone(), - bindings_rs: unpack_dir.join(manifest.bindings_rs), - target: manifest.target.clone(), - } -} + match fs::File::open(ar_src_dir.join(&archive_name)) { + Ok(_) => {} + Err(_) => { + let resp = reqwest::blocking::get(dl_link).unwrap(); + let mut bytes = io::Cursor::new(resp.bytes().unwrap()); -fn main() { - if cfg!(feature = "static-link") { - let target = build_support::Target::target(); - let version = env::var("CARGO_PKG_VERSION").unwrap(); - let mut feature_suffix = String::new(); - if cfg!(feature = "nozlib") { - feature_suffix.push_str("-nozlib"); + let mut file = fs::File::create(ar_src_dir.join(&archive_name)).unwrap(); + io::copy(&mut bytes, &mut file).unwrap(); + } } + } - let cache_tar_name = format!( - "russimp-{}-{}{}.tar.gz", - version, - target.to_string(), - feature_suffix - ); + dbg!(ar_src_dir.join(&archive_name)); - println!("cargo:rerun-if-env-changed=RUSSIMP_PREBUILT"); - let use_cache = env::var("RUSSIMP_PREBUILT").unwrap_or("ON".to_string()) != "OFF" - && cfg!(feature = "prebuilt"); - - let build_manifest = if use_cache { - download_from_cache(&cache_tar_name, &version) - } else { - let build_manifest = build_from_source(&target); - - // write build result to cache directory - println!("cargo:rerun-if-env-changed=RUSSIMP_BUILD_CACHE_DIR"); - if let Ok(cache_dir) = env::var("RUSSIMP_BUILD_CACHE_DIR") { - fs::create_dir_all(&cache_dir).unwrap(); - let output_tar_path = Path::new(&cache_dir).join(&cache_tar_name); - package(&build_manifest, output_tar_path); - } + let file = fs::File::open(ar_src_dir.join(&archive_name)).unwrap(); + let mut archive = tar::Archive::new(GzDecoder::new(file)); + let ar_dest_dir = out_dir.join(static_lib()); - build_manifest - }; + archive.unpack(&ar_dest_dir).unwrap(); - install(&build_manifest); - } else { - let (include, libdir, libname) = assimp_dynamic_linking(); - - if cfg!(target_os = "windows") { - let _ = Command::new("cargo") - .arg("vcpkg") - .arg("build") - .output() - .unwrap(); - } + println!( + "cargo:rustc-link-search=native={}", + ar_dest_dir.join("lib").display() + ); - run_bindgen( - &PathBuf::from(env::var("OUT_DIR").unwrap()).join(BINDINGS_FILE), - Some(&PathBuf::from(include)), - ).expect("russimp generate bindgen failed, for details see https://github.com/jkvargas/russimp-sys"); - println!("cargo:rustc-link-search=native={}", libdir); - println!("cargo:rustc-link-lib={}", libname); - } + println!( + "cargo:rustc-link-search=native={}", + ar_dest_dir.join("bin").display() + ); } -fn assimp_dynamic_linking() -> (String, String, String) { - let target = std::env::var("TARGET").unwrap(); - let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap_or("target/vcpkg".to_string()); - - let include_path = if target.contains("apple") { - "/opt/homebrew/opt/assimp/include" - } else if std::env::var("DOCS_RS").is_ok() { - "assimp/include" - } else { - "/usr/local/include" - }; +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let mut lib = vcpkg::Config::new() - .vcpkg_root(vcpkg_root.into()) - .find_package("assimp") - .unwrap_or(vcpkg::Library { - include_paths: vec![PathBuf::from(include_path)], - link_paths: vec![PathBuf::from("/usr/local/lib")], - found_names: vec!["assimp".to_owned()], - - ports: vec![], - cargo_metadata: vec![], - dll_paths: vec![], - found_dlls: vec![], - - is_static: false, - found_libs: vec![], - vcpkg_triplet: "".to_string(), - }); - - if cfg!(target_os = "windows") { - // Following dependencies are pulled in via Irrlicht. - // vcpkg doesn't know how to find these system dependencies, so we list them here. - println!("cargo:rustc-link-lib=user32"); - println!("cargo:rustc-link-lib=gdi32"); - lib.link_paths[0] = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join(lib.link_paths[0].as_path()) - .into(); + if cfg!(feature = "build-assimp") { + build_from_source(); + } else if cfg!(feature = "prebuilt") { + link_from_package(); } - ( - lib.include_paths[0].to_str().unwrap().to_owned(), - lib.link_paths[0].to_str().unwrap().to_owned(), - lib.found_names - .iter() - .filter(|n| n.starts_with("assimp")) - .nth(0) - .unwrap() - .to_owned(), - ) + bindgen::builder() + .header("wrapper.h") + .clang_arg(format!("-I{}", out_dir.join(static_lib()).join("include").display())) + .clang_arg(format!("-I{}", "assimp/include")) + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .allowlist_type("ai.*") + .allowlist_function("ai.*") + .allowlist_var("ai.*") + .allowlist_var("AI_.*") + .derive_partialeq(true) + .derive_eq(true) + .derive_hash(true) + .derive_debug(true) + .generate() + .unwrap() + .write_to_file(out_dir.join("bindings.rs")) + .expect("Could not generate russimp bindings, for details see https://github.com/jkvargas/russimp-sys"); + + let mut built_opts = built::Options::default(); + built_opts + .set_dependencies(false) + .set_compiler(false) + .set_ci(false) + .set_cfg(false); + + built::write_built_file_with_opts(&built_opts, &manifest_dir, &out_dir.join("built.rs")) + .unwrap(); + + for n in lib_names().iter() { + println!("cargo:rustc-link-lib={}={}", n.1, n.0); + } } diff --git a/build_support/download.rs b/build_support/download.rs deleted file mode 100644 index 08a329e..0000000 --- a/build_support/download.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::path::{Path, PathBuf}; -use std::{env, fs, io}; - -pub fn download(name: impl AsRef, url: impl AsRef) -> io::Result { - let resp = ureq::get(url.as_ref()).call(); - let download_dir = Path::new(&env::var("OUT_DIR").unwrap()).join("download"); - fs::create_dir_all(&download_dir).unwrap(); - let output_path = download_dir.join(name.as_ref()); - - match resp { - Ok(resp) => { - let mut reader = resp.into_reader(); - - let mut output_file = fs::File::create(&output_path)?; - io::copy(&mut reader, &mut output_file)?; - Ok(output_path) - } - Err(error) => Err(io::Error::new(io::ErrorKind::Other, error.to_string())), - } -} diff --git a/build_support/mod.rs b/build_support/mod.rs deleted file mode 100644 index a233bb8..0000000 --- a/build_support/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -mod download; -mod target; -use std::{io::ErrorKind, process::Command}; - -pub use download::*; -pub use target::*; - -pub fn run_command(cmd: &mut Command, program: &str) { - println!( - "current_dir: {:?}\nrunning: {:?}", - cmd.get_current_dir() - .map(|p| p.display().to_string()) - .unwrap_or("".to_string()), - cmd - ); - let status = match cmd.status() { - Ok(status) => status, - Err(ref e) if e.kind() == ErrorKind::NotFound => { - panic!( - "{}", - &format!( - "failed to execute command: {}\nis `{}` not installed?", - e, program - ) - ); - } - Err(e) => panic!("{}", &format!("failed to execute command: {:?}", e)), - }; - if !status.success() { - panic!( - "{}", - &format!("command did not execute successfully, got: {}", status) - ); - } -} - -#[cfg(any(target_os = "linux", target_os = "macos"))] -pub fn static_lib_filename(lib_name: &str) -> String { - format!("lib{}.a", lib_name) -} - -#[cfg(target_os = "windows")] -pub fn static_lib_filename(lib_name: &str) -> String { - format!("{}.lib", lib_name) -} diff --git a/build_support/target.rs b/build_support/target.rs deleted file mode 100644 index 65c8e77..0000000 --- a/build_support/target.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::{ - env, - fmt::{self, Display, Formatter}, -}; - -#[derive(Clone, Debug)] -pub struct Target { - pub architecture: String, - pub vendor: String, - pub system: String, - pub abi: Option, -} - -impl Display for Target { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!( - f, - "{}-{}-{}", - &self.architecture, &self.vendor, &self.system - )?; - - if let Some(ref abi) = self.abi { - write!(f, "-{}", abi) - } else { - Result::Ok(()) - } - } -} - -impl Target { - pub fn target() -> Target { - let target_str = env::var("TARGET").unwrap(); - Target::parse_target(target_str) - } - - pub fn parse_target(target_str: impl AsRef) -> Target { - let target_str = target_str.as_ref(); - let target: Vec = target_str.split('-').map(|s| s.into()).collect(); - assert!(target.len() >= 3, "Failed to parse TARGET {}", target_str); - - let abi = if target.len() > 3 { - Some(target[3].clone()) - } else { - None - }; - - Target { - architecture: target[0].clone(), - vendor: target[1].clone(), - system: target[2].clone(), - abi, - } - } -} diff --git a/install_assimp.bash b/install_assimp.bash deleted file mode 100644 index 227fc31..0000000 --- a/install_assimp.bash +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -path_apt_sourcelist=/etc/apt/sources.list -path_assimp_repo=/tmp/assimp -path_assimp_build="${path_assimp_repo}/build" - -if ! grep -q "apt.llvm.org" ${path_apt_sourcelist}; then - bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" -fi - -apt install -y git cmake ninja-build - -if [ ! -d ${path_assimp_repo} ]; then - git clone --depth 1 --branch v5.2.5 https://github.com/assimp/assimp.git ${path_assimp_repo} -fi - -if [ ! -d ${path_assimp_build} ]; then - mkdir ${path_assimp_build} - # shellcheck disable=SC2164 - cd ${path_assimp_build} - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_INSTALL_PREFIX=/usr/local -G Ninja .. - ninja - ninja install -fi \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index b6a70f5..8a1751f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1 @@ use_field_init_shorthand = true -newline_style = "Unix" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 827dcb2..ce48d8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,10 @@ #![allow(non_snake_case)] #![allow(improper_ctypes)] +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); impl From for String { diff --git a/wrapper.h b/wrapper.h index 50f1986..babe8f4 100644 --- a/wrapper.h +++ b/wrapper.h @@ -1,3 +1,21 @@ +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include \ No newline at end of file +#include +#include +#include +#include +#include +#include