Skip to content

Commit

Permalink
Enable system-installed HiGHS to be linked (#20)
Browse files Browse the repository at this point in the history
* Enable system-installed HiGHS to be linked

Instead of always building HiGHS, also allow for a version already
installed on the system (e.g. via a distro package manager) to be
linked.

* Add missing link in README.md

* Use new syntax for optional dependencies

* Add CI test for 'discover' feature on Linux and macOS
  • Loading branch information
Cu3PO42 authored Sep 14, 2023
1 parent 08237e1 commit c8d4213
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 55 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,13 @@ jobs:
run: cargo build
- name: Run tests
run: cargo test
- name: Build with system-installed HiGHS
if: matrix.config.os != 'windows-latest'
run: |
cd HiGHS
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
sudo cmake --install build
cd ..
cargo clean
cargo build --no-default-features --features "discover"
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ keywords = ["linear-programming", "optimization", "math", "solver"]

[build-dependencies]
bindgen = "0.63.0"
cmake = "0.1.49"
cmake = { version = "0.1.49", optional = true }
pkg-config = { version = "0.3.27", optional = true }

[features]
default = ["build", "highs_release"]
discover = ["dep:pkg-config"]
build = ["dep:cmake"]
highs_release = []
ninja = []
libz = []
66 changes: 46 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,48 @@ You should clone it with
git clone --recursive [email protected]:rust-or/highs-sys.git
```

## Dependencies
This crate can either use and link a version of HiGHS that is already installed and available on your system or build and statically link HiGHS itself.

This library depends on libstdc++ and libgomp at runtime.
In order to build, it requires cmake and a C++ compiler.
## Usage

#### Install on debian
At runtime, HiGHS depends at the minimum on the C++ standard library and OMP.
They need to be installed both on your system and any system you want to deploy your application to.

How you install these depends on your operating system.

#### Debian

```
sudo apt-get install libstdc++6 libgomp1
```

(These are probably already installed on your system)

#### Install on MacOS
#### macOS

```
brew install libomp
```

HiGHS itself is built statically, so you don't need to install it
separately on the target system.
### Building HiGHS

This crate can either build HiGHS itself and link it statically or [link against an already installed version](#using-a-pre-installed-version-of-highs).
To build HiGHS, you need at least a C++ compiler and cmake.
Enabling additional features may incur additional runtime dependencies.

#### Linux

These can be easily installed using your distribution's package manager.
For example, on Debian: `sudo apt install g++ cmake`.

#### Install on Windows
#### macOS

To install a C++ compiler, run `xcode-select --install`.
The easiest way to obtain cmake is via brew: `brew install cmake`.

If you enable the `libz` or `ninja` features, you should also install these via brew.

#### Windows

You need to install [CMake](https://cmake.org/download/) and [Clang (available in LLVM)](https://releases.llvm.org/download.html).

Expand All @@ -46,23 +65,30 @@ winget install -e --id Kitware.CMake
winget install -e --id LLVM.LLVM
```

#### Feature Flags

`highs_release`: set CMake profile to "Release" regardless of build profile.
`libz`: enable HiGHS libz linking to enable support for reading 'mps.gz'.
`ninja`: set CMake generator to Ninja.

Windows users will likely need to install libz and set the ZLIB_ROOT
environment variable for CMake to locate and link with the library.

Ninja is available in [winget](https://winget.run/).
If you enable the Ninja feature, you can also obtain Ninja from winget:

```powershell
winget install -e --id Ninja-build.Ninja
```

HiGHS itself is built statically, so you don't need to install it
separately on the target system.
If desired, libz needs to be installed and made discoverable by setting the `ZLIB_ROOT` environment variable.

### Using a pre-installed version of HiGHS

Rather than building HiGHS, you can link against a version you have already installed on your system.
To do that, install pkg-config on your system and enable the `discover` feature on this crate.

This will generally cause HiGHS to be linked dynamically, which means it also needs to be installed on the system you deploy to.

Note that at the time of writing, HiGHS is packaged in few package managers, so you may need to build and install HiGHS from source.

#### Feature Flags

`build` (enabled by default): build HiGHS and link it statically
`highs_release`: set CMake profile to "Release" regardless of build profile; only takes effect when `build` is enabled.
`libz`: enable HiGHS libz linking to enable support for reading 'mps.gz'; only takes effect when `build` is enabled.
`ninja`: set CMake generator to Ninja; only takes effect when `build` is enabled.
`discover`: use pkg-config to discover and link against an already installed version of HiGHS; takes precedence over `build` if both are enabled

## Example

Expand Down
105 changes: 71 additions & 34 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
use std::env;
use std::path::PathBuf;
use std::fs::write;
use std::path::{PathBuf, Path};

use cmake::Config;
fn generate_bindings<'a>(include_paths: impl Iterator<Item = &'a Path>) {
// First, we write a trivial wrapper header so that the HiGHS headers can be discovered from
// the include path.
let mut wrapper_path = PathBuf::from(env::var("OUT_DIR").unwrap());
wrapper_path.push("highs.h");
write(&wrapper_path, "#include <interfaces/highs_c_api.h>").unwrap();

fn main() {
// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let builder = include_paths.fold(bindgen::Builder::default(), |builder, path| builder.clang_arg(format!("-I{}", path.to_string_lossy())));
let c_bindings = builder
// The input header we would like to generate
// bindings for.
.header(wrapper_path.to_string_lossy())
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");

// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
c_bindings
.write_to_file(out_path.join("c_bindings.rs"))
.expect("Couldn't write bindings!");
}

#[cfg(feature = "build")]
fn build() -> bool {
use cmake::Config;
let mut dst = Config::new("HiGHS");

if cfg!(feature = "ninja") {
Expand All @@ -23,37 +54,7 @@ fn main() {
.build();

let include_path = dst.join("include").join("highs");
let src_path = PathBuf::from("HiGHS").join("src");

// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let c_bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header(
include_path
.join("interfaces")
.join("highs_c_api.h")
.to_string_lossy(),
)
.clang_args(&[
&format!("-I{}", include_path.to_string_lossy()),
&format!("-I{}", src_path.to_string_lossy()),
])
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");

// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
c_bindings
.write_to_file(out_path.join("c_bindings.rs"))
.expect("Couldn't write bindings!");
generate_bindings(Some(include_path.as_path()).into_iter());

println!("cargo:rustc-link-search=native={}/lib", dst.display());
println!("cargo:rustc-link-search=native={}/lib64", dst.display());
Expand All @@ -79,4 +80,40 @@ fn main() {
println!("cargo:rustc-link-lib=dylib=gomp");
}
println!("cargo:rerun-if-changed=HiGHS/src/interfaces/highs_c_api.h");

true
}

#[cfg(not(feature = "build"))]
fn build() -> bool {
false
}

#[cfg(feature = "discover")]
fn discover() -> bool {
let lib = match pkg_config::Config::new().atleast_version("1.5.0").probe("highs") {
Ok(lib) => lib,
Err(_e) => return false,
};

generate_bindings(lib.include_paths.iter().map(|p| p.as_path()));

true

}

#[cfg(not(feature = "discover"))]
fn discover() -> bool {
false
}

fn main() {
if cfg!(all(any(feature = "highs_release", feature = "libz", feature = "ninja"), not(feature = "build"))) {
panic!("You have enabled features that control how HiGHS is built, but have not enabled the 'build' feature.\n\
Thus, your features will never have any effect. Please enable the 'build' feature on highs-sys if you want to build HiGHS or disable the 'libz', 'ninja' and 'highs_release' features.");
}

if !discover() && !build() {
panic!("Could neither discover nor build HiGHS");
}
}

0 comments on commit c8d4213

Please sign in to comment.