Enable building with framework version of OpenCV on macOS
MODIFY
.github/workflows/opencv-rust.yml
.github/workflows/opencv-rust.yml
@@ -9,8 +9,7 @@ on:
branches:
- "*"
jobs:
- test:
- runs-on: ${{ matrix.os.image }}
+ src:
strategy:
fail-fast: false
matrix:
@@ -26,21 +25,19 @@ jobs:
- beta
version:
- opencv: 3.4.9
- brew: "@3"
vcpkg: 3
features: opencv-34
- opencv: 4.2.0
- brew:
vcpkg: 4
features: opencv-4
+ runs-on: ${{ matrix.os.image }}
env:
+ OS_FAMILY: ${{ matrix.os.family }}
+ RUST_TOOLCHAIN: ${{ matrix.rust }}
Atlas_ROOT_DIR: /usr/include/ # for cmake to find lapacke.h
OPENCV_VERSION: ${{ matrix.version.opencv }}
- MACOS_OPENCV_VERSION: ${{ matrix.version.brew }}
VCPKG_OPENCV_VERSION: ${{ matrix.version.vcpkg }}
CARGO_FEATURES: ${{ matrix.version.features }}
- OS_FAMILY: ${{ matrix.os.family }}
- RUST_TOOLCHAIN: ${{ matrix.rust }}
steps:
- uses: actions/checkout@v2
@@ -71,25 +68,32 @@ jobs:
name: generated-failed-bindings-${{ runner.os }}-${{ matrix.version.opencv }}
path: target
- windows-choco:
- runs-on: windows-2019
+ bin:
strategy:
fail-fast: false
matrix:
+ os:
+ - image: macos-10.15
+ family: osx
+ - image: windows-2019
+ family: windows
rust:
- stable
- beta
version:
- choco: 3.4.8
+ brew: "@3"
features: opencv-34
- choco: 4.1.2
+ brew: "@4"
features: opencv-4
+ runs-on: ${{ matrix.os.image }}
env:
+ OS_FAMILY: ${{ matrix.os.family }}
+ RUST_TOOLCHAIN: ${{ matrix.rust }}
CHOCO_OPENCV_VERSION: ${{ matrix.version.choco }}
+ BREW_OPENCV_VERSION: ${{ matrix.version.brew }}
CARGO_FEATURES: ${{ matrix.version.features }}
- OS_FAMILY: windows
- RUST_TOOLCHAIN: ${{ matrix.rust }}
-
steps:
- uses: actions/checkout@v2
@@ -111,16 +115,16 @@ jobs:
uses: actions/upload-artifact@v1
if: failure()
with:
- name: generated-failed-bindings-${{ runner.os }}-${{ matrix.version.opencv }}
+ name: generated-failed-bindings-${{ runner.os }}-${{ matrix.version.choco }}
path: target
- linux-opencv-32:
+ lnx-32:
runs-on: ubuntu-18.04
env:
- OPENCV_VERSION: 3.2.0
- CARGO_FEATURES: opencv-32
OS_FAMILY: linux
RUST_TOOLCHAIN: stable
+ OPENCV_VERSION: 3.2.0
+ CARGO_FEATURES: opencv-32
steps:
- uses: actions/checkout@v2
@@ -142,6 +146,5 @@ jobs:
uses: actions/upload-artifact@v1
if: failure()
with:
- name: generated-failed-bindings-${{ runner.os }}-${{ matrix.version.opencv }}
+ name: generated-failed-bindings-${{ runner.os }}-3.2.0
path: target
-

MODIFY
README.md
README.md
@@ -133,7 +133,9 @@ The following variables must be set when building without `pkg_config` or `vcpkg
other platforms, then `pkg_config` or `vcpkg` usage will be disabled and the set values will be used.
* `OPENCV_LINK_LIBS`
- Comma separated list of library names to link to. `.lib`, `.so` or `.dylib` extension is optional.
+ Comma separated list of library names to link to. `.lib`, `.so` or `.dylib` extension is optional. If you
+ specify the ".framework" extension then build script will link a macOS framework instead of plain shared
+ library.
E.g. "opencv_world411".
* `OPENCV_LINK_PATHS`
@@ -142,7 +144,7 @@ other platforms, then `pkg_config` or `vcpkg` usage will be disabled and the set
* `OPENCV_INCLUDE_PATHS`
Comma separated list of paths to search for system include files during compilation. E.g.
"C:\tools\opencv\build\include". One of the directories specified therein must contain
- "opencv2/core/version.hpp" file, it's used to detect the version of the headers.
+ "opencv2/core/version.hpp" or "core/version.hpp" file, it's used to detect the version of the headers.
The following variables are optional, but you might need to set them under some circumstances:
@@ -161,7 +163,7 @@ The following variables are optional, but you might need to set them under some
The following variables affect the building the of the `opencv` crate, but belong to external components:
* `PKG_CONFIG_PATH`
- Where to look for `*.pc` files see the [man page of `pkg-config`](https://linux.die.net/man/1/pkg-config)
+ Where to look for `*.pc` files see the [man pkg-config](https://linux.die.net/man/1/pkg-config)
Path specified here must contain `opencv.pc` or `opencv4.pc` (for OpenCV 4.x).
* `VCPKG_ROOT` and `VCPKGRS_DYNAMIC`
@@ -169,10 +171,15 @@ The following variables affect the building the of the `opencv` crate, but belon
[documentation for `vcpkg` crate](https://docs.rs/vcpkg)
* `LD_LIBRARY_PATH`
- On Linux and macOS it sets the list of directories to look for the installed `*.so` files during runtime.
+ On Linux it sets the list of directories to look for the installed `*.so` files during runtime.
[Linux documentation](https://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html) has more info.
Path specified here must contain `libopencv_*.so` files.
+* `DYLD_LIBRARY_PATH`
+ Similar to `LD_LIBRARY_PATH`, but for loading `*.dylib` files on macOS, see
+ [man dyld](https://man.cx/dyld(1)) for more info.
+ Path specified here must contain `*.dylib` files.
+
* `PATH`
Windows searches for `*.dll`s in `PATH` among other places, be sure to set it up, or copy required OpenCV
`*.dll`s next to your binary. Be sure to specify paths in UNIX style (/C/Program Files/Dir) because colon
@@ -219,11 +226,12 @@ Currently the main development and testing of the crate is performed on Linux, b
also supported: macOS and Windows.
For some more details please refer to the CI build scripts:
-[Linux OpenCV install](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-bionic.sh)
-[macOS OpenCV install](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-macos.sh)
-[Windows OpenCV install via Chocolatey](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-windows-chocolatey.sh)
-[Windows OpenCV install via vcpkg](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-windows-vcpkg.sh)
-[Crate build script](https://github.com/twistedfall/opencv-rust/blob/master/ci/script.sh).
+[Linux OpenCV install](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-bionic.sh),
+[macOS OpenCV install as framework](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-macos-framework.sh),
+[macOS OpenCV install via brew](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-macos-brew.sh),
+[Windows OpenCV install via Chocolatey](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-windows-chocolatey.sh),
+[Windows OpenCV install via vcpkg](https://github.com/twistedfall/opencv-rust/blob/master/ci/install-windows-vcpkg.sh),
+[Test runner script](https://github.com/twistedfall/opencv-rust/blob/master/ci/script.sh).
### Functionality

MODIFY
build.rs
build.rs
@@ -5,7 +5,7 @@ use std::{
ffi::OsStr,
fs::{self, File},
io::{BufRead, BufReader},
- iter::FromIterator,
+ iter::{self, FromIterator},
path::{Path, PathBuf},
};
@@ -99,7 +99,7 @@ mod generator {
modules.par_iter().for_each(|module| {
let mut module_file = SRC_CPP_DIR.join(format!("{}.hpp", module));
if !module_file.exists() {
- module_file = opencv_header_dir.join(format!("opencv2/{}.hpp", module));
+ module_file = opencv_dir.join(format!("{}.hpp", module));
}
let bindings_writer = binding_generator::writer::RustBindingWriter::new(
&*SRC_CPP_DIR,
@@ -293,14 +293,24 @@ struct Library {
}
impl Library {
- fn strip_library_extensions(libs: impl IntoIterator<Item=impl Into<PathBuf>>) -> impl Iterator<Item=String> {
+ fn process_library_list(libs: impl IntoIterator<Item=impl Into<PathBuf>>) -> impl Iterator<Item=String> {
libs.into_iter()
.map(|x| {
let mut path: PathBuf = x.into();
+ let extension = path.extension().and_then(OsStr::to_str).unwrap_or_default();
+ let is_framework = extension.eq_ignore_ascii_case("framework");
+ const LIB_EXTS: [&str; 7] = ["so", "a", "dll", "lib", "dylib", "framework", "tbd"];
+ if is_framework || LIB_EXTS.iter().any(|e| e.eq_ignore_ascii_case(extension)) {
path.set_extension("");
+ }
path.file_name()
- .and_then(|f| f.to_str()).expect("Invalid library name")
- .to_owned()
+ .and_then(|f| f.to_str()
+ .map(|f| if is_framework {
+ format!("framework={}", f)
+ } else {
+ f.to_owned()
+ })
+ ).expect("Invalid library name")
})
}
@@ -311,27 +321,32 @@ impl Library {
eprintln!("=== link_paths: {}", link_paths);
eprintln!("=== include_paths: {}", include_paths);
let mut cargo_metadata = Vec::with_capacity(64);
- cargo_metadata.extend(link_paths.split(',')
- .filter_map(|path| {
- let path = path.trim();
- if path.is_empty() {
- None
- } else {
- Some(format!("cargo:rustc-link-search=native={}", path))
+ cargo_metadata.extend(
+ link_paths.split(',')
+ .map(str::trim)
+ .filter(|x| !x.is_empty())
+ .map(|path| {
+ let out = iter::once(format!("cargo:rustc-link-search={}", path));
+ #[cfg(target_os = "macos")] {
+ out.chain(
+ iter::once(format!("cargo:rustc-link-search=framework={}", path))
+ )
+ }
+ #[cfg(not(target_os = "macos"))] {
+ out
}
})
- );
-
- let libs = Self::strip_library_extensions(
+ .flatten()
+ .chain(Self::process_library_list(
link_libs.split(',')
- .map(|x| x.trim())
- .filter(|x| !x.is_empty())
+ .map(str::trim)
+ .filter(|x| !x.is_empty()))
+ .map(|l| format!("cargo:rustc-link-lib={}", l))
+ )
);
- cargo_metadata.extend(libs.map(|l| format!("cargo:rustc-link-lib={}", l)));
-
let include_paths: Vec<_> = include_paths.split(',')
- .map(|x| x.trim())
+ .map(str::trim)
.filter(|x| !x.is_empty())
.map(PathBuf::from)
.collect();
@@ -358,7 +373,16 @@ impl Library {
cargo_metadata.extend(
opencv.link_paths.iter()
.map(|link_path| format!("cargo:rustc-link-search=native={}", link_path.to_str().expect("Invalid link path")))
- .chain(opencv.libs.iter().map(|lib| format!("cargo:rustc-link-lib={}", lib))));
+ .chain(opencv.libs.iter()
+ .map(|lib| format!("cargo:rustc-link-lib={}", lib))
+ )
+ .chain(opencv.framework_paths.iter()
+ .map(|fw_path| format!("cargo:rustc-link-search=framework={}", fw_path.to_str().expect("Invalid framework path")))
+ )
+ .chain(opencv.frameworks.iter()
+ .map(|fw| format!("rustc-link-lib=framework={}", fw))
+ )
+ );
Ok(Self {
pkg_name: pkg_name.to_owned(),
include_paths: opencv.include_paths,
@@ -437,10 +461,19 @@ fn file_copy_to_dir(src_file: &Path, target_dir: &Path) -> Result<PathBuf> {
}
fn get_version_from_headers(header_dir: &PathBuf) -> Option<String> {
- let version_hpp = header_dir.join("opencv2/core/version.hpp");
- if !version_hpp.is_file() {
+ let version_hpp = {
+ let out = header_dir.join("opencv2/core/version.hpp");
+ if out.is_file() {
+ out
+ } else {
+ let out = header_dir.join("Headers/core/version.hpp");
+ if out.is_file() {
+ out
+ } else {
return None;
}
+ }
+ };
let mut major = None;
let mut minor = None;
let mut revision = None;
@@ -664,12 +697,12 @@ fn main() -> Result<()> {
eprintln!("=== OpenCV library configuration: {:#?}", opencv);
if !cfg!(feature = "docs-only") {
check_matching_version(&opencv.version)
- .map_err(|e| format!("{}, (version coming from pkg_config for package: {})", e, opencv.pkg_name))?;
+ .map_err(|e| format!("{}, (version coming from the detected package/environment)", e))?;
}
let opencv_header_dir = env::var_os("OPENCV_HEADER_DIR").map(PathBuf::from).unwrap_or_else(|| {
if cfg!(feature = "buildtime-bindgen") {
opencv.include_paths.iter()
- .find(|p| p.join("opencv2").exists())
+ .find(|p| p.join("opencv2/core/version.hpp").is_file() || p.join("Headers/core/version.hpp").is_file())
.expect("Using buildtime-bindgen, but discovered OpenCV include paths is empty or contains non-existent paths").clone()
} else if cfg!(feature = "opencv-32") {
MANIFEST_DIR.join("headers/3.2")

MODIFY
ci/install-bionic.sh
ci/install-bionic.sh
@@ -141,17 +141,20 @@ sudo ln -s /usr/lib/llvm-6.0/lib/libclang.so.1 /usr/lib/llvm-6.0/lib/libclang.so
if [[ "$OPENCV_VERSION" == "3.2.0" ]]; then
sudo apt -y install libopencv-dev=3.2.0*
else
- mkdir -p ~/build/opencv
+ base_dir="$HOME/build/opencv/"
+ mkdir -p "$base_dir"
- wget -q -O- "https://github.com/opencv/opencv/archive/$OPENCV_VERSION.tar.gz" | tar -xzC ~/build/opencv
- wget -q -O- "https://github.com/opencv/opencv_contrib/archive/$OPENCV_VERSION.tar.gz" | tar -xzC ~/build/opencv
+ wget -q -O- "https://github.com/opencv/opencv/archive/$OPENCV_VERSION.tar.gz" | tar -xzC "$base_dir"
+ wget -q -O- "https://github.com/opencv/opencv_contrib/archive/$OPENCV_VERSION.tar.gz" | tar -xzC "$base_dir"
- mkdir -p ~/"build/opencv/opencv-$OPENCV_VERSION-build"
- pushd ~/"build/opencv/opencv-$OPENCV_VERSION-build" > /dev/null
+ build_dir="$base_dir/opencv-$OPENCV_VERSION-build/"
+ mkdir -p "$build_dir"
+
+ pushd "$build_dir" > /dev/null
cmake $BUILD_FLAGS \
-D CMAKE_INSTALL_PREFIX=/usr \
- -D OPENCV_EXTRA_MODULES_PATH=~/"build/opencv/opencv_contrib-$OPENCV_VERSION/modules" \
- ~/"build/opencv/opencv-$OPENCV_VERSION"
+ -D OPENCV_EXTRA_MODULES_PATH="$base_dir/opencv_contrib-$OPENCV_VERSION/modules" \
+ "$base_dir/opencv-$OPENCV_VERSION"
sudo make -j"$(nproc)" install
popd > /dev/null
fi

ADD
ci/install-macos-brew.sh
+#!/bin/bash
+
+set -vex
+
+brew unlink python@2
+brew install pkg-config
+brew install opencv"$BREW_OPENCV_VERSION"

ADD
ci/install-macos-framework.sh
+#!/bin/bash
+
+set -vex
+
+base_dir="$HOME/build/opencv/"
+mkdir -p "$base_dir"
+
+wget -q -O- "https://github.com/opencv/opencv/archive/$OPENCV_VERSION.tar.gz" | tar -xzC "$base_dir"
+wget -q -O- "https://github.com/opencv/opencv_contrib/archive/$OPENCV_VERSION.tar.gz" | tar -xzC "$base_dir"
+
+build_dir="$base_dir/opencv-$OPENCV_VERSION-build/"
+mkdir -p "$build_dir"
+
+python "$base_dir/opencv-$OPENCV_VERSION/platforms/osx/build_framework.py" \
+ --contrib "$base_dir/opencv_contrib-$OPENCV_VERSION" \
+ --enable_nonfree \
+ "$build_dir"

DELETE
ci/install-macos.sh
-#!/bin/bash
-
-set -vex
-
-brew unlink python@2
-brew install llvm pkg-config
-brew install opencv"$MACOS_OPENCV_VERSION"
-# https://stackoverflow.com/a/53331571/1433768
-#brew cleanup

MODIFY
ci/install.sh
ci/install.sh
@@ -7,7 +7,11 @@ ci_dir="$(dirname "$0")"
if [[ "$OS_FAMILY" == "linux" ]]; then
"$ci_dir/install-bionic.sh"
elif [[ "$OS_FAMILY" == "osx" ]]; then
- "$ci_dir/install-macos.sh"
+ if [[ "$BREW_OPENCV_VERSION" != "" ]]; then # brew build
+ "$ci_dir/install-macos-brew.sh"
+ else
+ "$ci_dir/install-macos-framework.sh"
+ fi
elif [[ "$OS_FAMILY" == "windows" ]]; then
if [[ "$CHOCO_OPENCV_VERSION" != "" ]]; then # chocolatey build
"$ci_dir/install-windows-chocolatey.sh"

MODIFY
ci/script.sh
ci/script.sh
@@ -20,10 +20,24 @@ if [[ "$OS_FAMILY" == "windows" ]]; then
echo "=== Installed chocolatey packages:"
choco list --local-only
elif [[ "$OS_FAMILY" == "osx" ]]; then
- if [[ "$MACOS_OPENCV_VERSION" == "@3" ]]; then
+ CARGO_FEATURES="$CARGO_FEATURES,contrib"
+ # fixme, enable building with xcode libclang
+# xcode_path="$(xcode-select --print-path)/"
+# toolchain_path="$xcode_path/Toolchains/XcodeDefault.xctoolchain/"
+# export LIBCLANG_PATH="$toolchain_path/usr/lib/libclang.dylib"
+# export CLANG_PATH="$toolchain_path/usr/bin/clang"
+# export DYLD_LIBRARY_PATH="$toolchain_path/usr/lib/"
+ if [[ "$BREW_OPENCV_VERSION" != "" ]]; then # brew build
+ if [[ "$BREW_OPENCV_VERSION" == "@3" ]]; then
export PKG_CONFIG_PATH="/usr/local/opt/opencv@3/lib/pkgconfig"
fi
- CARGO_FEATURES="$CARGO_FEATURES,contrib"
+ else # framework build
+ clang_dir="$(clang --print-search-dirs | awk -F= '/^libraries: =/ { print $2 }')"
+ export OPENCV_LINK_PATHS="$HOME/build/opencv/opencv-$OPENCV_VERSION-build,$clang_dir/lib/darwin"
+ export OPENCV_LINK_LIBS="opencv2.framework,OpenCL.framework,Cocoa.framework,Accelerate.framework,AVFoundation.framework,CoreGraphics.framework,CoreMedia.framework,CoreVideo.framework,QuartzCore.framework,clang_rt.osx"
+# export OPENCV_INCLUDE_PATHS="$HOME/build/opencv/opencv-$OPENCV_VERSION-build/opencv2.framework"
+ export OPENCV_INCLUDE_PATHS="$HOME/build/opencv/opencv-$OPENCV_VERSION-build/build/build-x86_64-macosx/install/include"
+ fi
elif [[ "$OS_FAMILY" == "linux" ]]; then
if [[ "$OPENCV_VERSION" != "3.2.0" ]]; then
# 3.2.0 version from the repository doesn't have dnn module, that's why contrib feature is not enabled
@@ -39,6 +53,8 @@ rustc --print=cfg
cargo test -vv -p opencv-binding-generator
+# todo: test without contrib too
+
# only possible to build linux without `buildtime-bindgen`
if [[ "$OS_FAMILY" == "linux" ]]; then
cargo test -vv --no-default-features --features "$CARGO_FEATURES"

Enable building with framework version of OpenCV on macOS