From 8bef87c273dd62f3d9f13053a76343b8f43f12f4 Mon Sep 17 00:00:00 2001 From: bghira Date: Mon, 24 Aug 2026 17:11:17 -0600 Subject: [PATCH 1/5] Build wheels for Python 3.14 --- .github/workflows/ci.yml | 5 ++- .github/workflows/publish.yml | 38 +++++++++++++++----- Cargo.toml | 4 +-- pyproject.toml | 7 ++-- src/python_bindings.rs | 66 +++++++++++++++++------------------ 5 files changed, 71 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1013ba1..8e2320b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,7 +108,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -387,7 +387,7 @@ jobs: runs-on: ubuntu-24.04-arm strategy: matrix: - python: ["3.11", "3.12", "3.13"] + python: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -434,7 +434,6 @@ jobs: container: off manylinux: manylinux_2_39 env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: '1' LIBCLANG_PATH: ${{ env.LIBCLANG_PATH }} LLVM_CONFIG_PATH: ${{ env.LLVM_CONFIG_PATH }} OPENCV_INCLUDE_PATHS: ${{ github.workspace }}/third_party/opencv-static/include/opencv4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ad6dae7..0744254 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,6 +25,10 @@ jobs: target: x86_64 manylinux: manylinux_2_35 python: '3.13' + - os: ubuntu-22.04 + target: x86_64 + manylinux: manylinux_2_35 + python: '3.14' # Ubuntu 24.04 (libc 2.39) - os: ubuntu-24.04 target: x86_64 @@ -38,6 +42,10 @@ jobs: target: x86_64 manylinux: manylinux_2_39 python: '3.13' + - os: ubuntu-24.04 + target: x86_64 + manylinux: manylinux_2_39 + python: '3.14' steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -104,8 +112,6 @@ jobs: container: off manylinux: ${{ matrix.manylinux }} env: - # Use stable ABI for forward compatibility - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: '1' # LLVM for opencv-rust bindings (from earlier install step) LIBCLANG_PATH: ${{ env.LIBCLANG_PATH }} LLVM_CONFIG_PATH: ${{ env.LLVM_CONFIG_PATH }} @@ -135,7 +141,7 @@ jobs: runs-on: ubuntu-24.04-arm strategy: matrix: - python: ['3.11', '3.12', '3.13'] + python: ['3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -186,8 +192,6 @@ jobs: container: off manylinux: manylinux_2_39 env: - # Use stable ABI for forward compatibility - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: '1' # LLVM for opencv-rust bindings (from earlier install step) LIBCLANG_PATH: ${{ env.LIBCLANG_PATH }} LLVM_CONFIG_PATH: ${{ env.LLVM_CONFIG_PATH }} @@ -217,7 +221,7 @@ jobs: strategy: matrix: target: [aarch64] - python: ['3.11', '3.12', '3.13'] + python: ['3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -251,8 +255,6 @@ jobs: args: --release --out dist -i python --no-default-features --features python-bindings,simd,opencv,metal sccache: 'false' env: - # Use stable ABI for forward compatibility - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: '1' # OpenCV static linking configuration OPENCV_INCLUDE_PATHS: ${{ github.workspace }}/third_party/opencv-static/include/opencv4 OPENCV_LINK_PATHS: ${{ github.workspace }}/third_party/opencv-static/lib @@ -306,6 +308,11 @@ jobs: manylinux: manylinux_2_35 python: '3.13' artifact_name: wheels-ubuntu-22.04-x86_64-py3.13 + - os: ubuntu-22.04 + target: x86_64 + manylinux: manylinux_2_35 + python: '3.14' + artifact_name: wheels-ubuntu-22.04-x86_64-py3.14 # Ubuntu 24.04 (libc 2.39) - os: ubuntu-24.04 target: x86_64 @@ -322,6 +329,11 @@ jobs: manylinux: manylinux_2_39 python: '3.13' artifact_name: wheels-ubuntu-24.04-x86_64-py3.13 + - os: ubuntu-24.04 + target: x86_64 + manylinux: manylinux_2_39 + python: '3.14' + artifact_name: wheels-ubuntu-24.04-x86_64-py3.14 # Linux aarch64 (DGX Spark / GB10) - os: ubuntu-24.04-arm target: aarch64 @@ -338,6 +350,11 @@ jobs: manylinux: manylinux_2_39 python: '3.13' artifact_name: wheels-linux-aarch64-py3.13 + - os: ubuntu-24.04-arm + target: aarch64 + manylinux: manylinux_2_39 + python: '3.14' + artifact_name: wheels-linux-aarch64-py3.14 # macOS aarch64 - os: macos-latest target: aarch64 @@ -354,6 +371,11 @@ jobs: manylinux: '' python: '3.13' artifact_name: wheels-macos-latest-aarch64-py3.13 + - os: macos-latest + target: aarch64 + manylinux: '' + python: '3.14' + artifact_name: wheels-macos-latest-aarch64-py3.14 steps: - uses: actions/download-artifact@v4 with: diff --git a/Cargo.toml b/Cargo.toml index 8b9a728..6e7c5ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,13 @@ imageproc = "0.25" metal = {version = "0.27", optional = true} ndarray = "0.16" num_cpus = "1.16" -numpy = {version = "0.22", optional = true} +numpy = {version = "0.27", optional = true} objc = {version = "0.2", optional = true} # OpenCV bindings for performance parity # Limit binding generation to the modules this crate actually uses. opencv = {version = "0.99", optional = true, default-features = false, features = ["clang-runtime", "imgproc", "videoio"]} # Python bindings (optional) -pyo3 = {version = "0.22", features = ["extension-module"], optional = true} +pyo3 = {version = "0.27", features = ["extension-module"], optional = true} rayon = "1.8" tempfile = {version = "3.10", optional = true} # SIMD optimizations diff --git a/pyproject.toml b/pyproject.toml index 79f690b..36a45fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] build-backend = "maturin" -requires = ["maturin>=1.0,<2.0"] +requires = ["maturin>=1.9.5,<2.0"] [project] authors = [{name = "bghira", email = "bghira@users.noreply.github.com"}] @@ -10,7 +10,8 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13" + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14" ] dependencies = ["numpy>=1.21.0", "pillow>=8.0.0"] description = "High-performance Rust reimplementation of GIL-blocking video/image operations" @@ -20,7 +21,7 @@ requires-python = ">=3.11" version = "0.3.1" [project.optional-dependencies] -dev = ["pytest>=6.0", "pytest-benchmark", "maturin>=1.0,<2.0", "pre-commit"] +dev = ["pytest>=6.0", "pytest-benchmark", "maturin>=1.9.5,<2.0", "pre-commit"] [tool.black] extend-exclude = ''' diff --git a/src/python_bindings.rs b/src/python_bindings.rs index f1f2b49..49fd19a 100644 --- a/src/python_bindings.rs +++ b/src/python_bindings.rs @@ -88,7 +88,7 @@ impl BufferPool { #[cfg(feature = "python-bindings")] #[pyfunction] -pub fn load_image_batch(py: Python, image_paths: Vec) -> PyResult> { +pub fn load_image_batch(py: Python, image_paths: Vec) -> PyResult>> { use rayon::prelude::*; let results: Vec<_> = image_paths @@ -100,7 +100,7 @@ pub fn load_image_batch(py: Python, image_paths: Vec) -> PyResult { - let py_bytes = PyBytes::new_bound(py, &image_data); + let py_bytes = PyBytes::new(py, &image_data); py_results.push(py_bytes.into_any().unbind()); } Err(_) => { @@ -155,7 +155,7 @@ pub unsafe fn batch_crop_images_zero_copy<'py>( let array = ndarray::Array3::from_shape_vec((height, width, channels), output_buffer) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Shape error: {}", e)))?; - let py_array = PyArray3::from_owned_array_bound(py, array); + let py_array = PyArray3::from_owned_array(py, array); py_results.push(py_array); } @@ -206,7 +206,7 @@ pub unsafe fn batch_center_crop_images_zero_copy<'py>( .map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!("Shape error: {}", e)) })?; - let py_array = PyArray3::from_owned_array_bound(py, array); + let py_array = PyArray3::from_owned_array(py, array); py_results.push(py_array); } @@ -390,10 +390,10 @@ pub fn batch_resize_images_zero_copy<'py>( // Convert to PyArray3 and return as Python list let py_results: Vec>> = results .into_iter() - .map(|array| PyArray3::from_owned_array_bound(py, array)) + .map(|array| PyArray3::from_owned_array(py, array)) .collect(); - Ok(PyList::new_bound(py, py_results).into_any()) + Ok(PyList::new(py, py_results)?.into_any()) } /// Ultra-fast OpenCV-powered resize using zero-copy Mat headers @@ -552,7 +552,7 @@ fn resize_single_image_direct<'py>( })?; // DIRECT return - no Vec wrapper overhead! - Ok(PyArray3::from_owned_array_bound(py, result)) + Ok(PyArray3::from_owned_array(py, result)) } #[cfg(not(feature = "opencv"))] @@ -578,12 +578,12 @@ fn is_tuple_or_list_of_two(obj: &Bound) -> bool { use pyo3::types::{PyList, PyTuple}; // Check if it's a tuple of length 2 - if let Ok(tuple) = obj.downcast::() { + if let Ok(tuple) = obj.cast::() { return tuple.len() == 2; } // Check if it's a list of length 2 containing numbers (not nested lists) - if let Ok(list) = obj.downcast::() { + if let Ok(list) = obj.cast::() { if list.len() == 2 { // Check if first element is a number (not a nested list/tuple) if let Ok(first) = list.get_item(0) { @@ -616,7 +616,7 @@ impl ResizeIterator { // Transfer the completed allocation directly into the ndarray instead // of cloning the full resized image during iteration. match ndarray::Array3::from_shape_vec((height, width, channels), buffer) { - Ok(array) => Some(PyArray3::from_owned_array_bound(py, array)), + Ok(array) => Some(PyArray3::from_owned_array(py, array)), Err(_) => None, // Malformed buffer; end iteration (StopIteration) } } @@ -872,7 +872,7 @@ pub fn batch_crop_images<'py>( let img_view = image.as_array(); match crop_image_array(&img_view, x, y, width, height) { Ok(cropped) => { - let py_array = PyArray3::from_owned_array_bound(py, cropped); + let py_array = PyArray3::from_owned_array(py, cropped); py_results.push(py_array); } Err(e) => { @@ -905,7 +905,7 @@ pub fn batch_center_crop_images<'py>( let img_view = image.as_array(); match crate::cropping::center_crop_image_array(&img_view, target_width, target_height) { Ok(cropped) => { - let py_array = PyArray3::from_owned_array_bound(py, cropped); + let py_array = PyArray3::from_owned_array(py, cropped); py_results.push(py_array); } Err(e) => { @@ -932,7 +932,7 @@ pub fn batch_random_crop_images<'py>( let img_view = image.as_array(); match random_crop_image_array(&img_view, target_width, target_height) { Ok(cropped) => { - let py_array = PyArray3::from_owned_array_bound(py, cropped); + let py_array = PyArray3::from_owned_array(py, cropped); py_results.push(py_array); } Err(e) => { @@ -958,7 +958,7 @@ pub fn batch_calculate_luminance( let image_views: Vec<_> = images.iter().map(|image| image.as_array()).collect(); - Ok(py.allow_threads(|| { + Ok(py.detach(|| { image_views .par_iter() .map(crate::luminance::calculate_luminance_array_sequential) @@ -984,7 +984,7 @@ pub fn rgb_to_rgba_optimized<'py>( let rgba_array = ndarray::Array3::from_shape_vec((height, width, 4), rgba_data) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Shape error: {}", e)))?; - let py_array = PyArray3::from_owned_array_bound(py, rgba_array); + let py_array = PyArray3::from_owned_array(py, rgba_array); Ok((py_array, metrics.throughput_mpixels_per_sec)) } @@ -1013,7 +1013,7 @@ pub fn rgba_to_rgb_optimized<'py>( let rgb_array = ndarray::Array3::from_shape_vec((height, width, 3), rgb_data) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Shape error: {}", e)))?; - let py_array = PyArray3::from_owned_array_bound(py, rgb_array); + let py_array = PyArray3::from_owned_array(py, rgb_array); Ok((py_array, metrics.throughput_mpixels_per_sec)) } @@ -1057,7 +1057,7 @@ pub fn batch_resize_images<'py>( Ok(resized_images) => { let py_results: Vec<_> = resized_images .into_iter() - .map(|resized| PyArray3::from_owned_array_bound(py, resized)) + .map(|resized| PyArray3::from_owned_array(py, resized)) .collect(); Ok(py_results) } @@ -1095,7 +1095,7 @@ pub fn batch_resize_videos<'py>( Ok(resized_videos) => { let py_results: Vec<_> = resized_videos .into_iter() - .map(|resized| PyArray4::from_owned_array_bound(py, resized)) + .map(|resized| PyArray4::from_owned_array(py, resized)) .collect(); Ok(py_results) } @@ -1134,7 +1134,7 @@ pub fn resize_bilinear_opencv<'py>( match resize_bilinear_opencv(&image_array, target_width, target_height) { Ok(resized) => { - let py_array = PyArray3::from_owned_array_bound(py, resized); + let py_array = PyArray3::from_owned_array(py, resized); Ok(py_array) } Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!( @@ -1158,7 +1158,7 @@ pub fn resize_lanczos4_opencv<'py>( match resize_lanczos4_opencv(&image_array, target_width, target_height) { Ok(resized) => { - let py_array = PyArray3::from_owned_array_bound(py, resized); + let py_array = PyArray3::from_owned_array(py, resized); Ok(py_array) } Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!( @@ -1218,11 +1218,11 @@ pub fn imdecode_py<'py>( } }; - let decoded = py.allow_threads(move || imdecode(&buf, imread_flags)); + let decoded = py.detach(move || imdecode(&buf, imread_flags)); match decoded { Ok(image) => { - let py_array = PyArray3::from_owned_array_bound(py, image); + let py_array = PyArray3::from_owned_array(py, image); Ok(py_array) } Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!( @@ -1258,7 +1258,7 @@ pub fn cvt_color_py<'py>( let src_array = src.as_array(); match cvt_color(&src_array, color_code) { Ok(converted) => { - let py_array = PyArray3::from_owned_array_bound(py, converted); + let py_array = PyArray3::from_owned_array(py, converted); Ok(py_array) } Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!( @@ -1281,7 +1281,7 @@ pub fn canny_py<'py>( let image_array = image.as_array(); match canny(&image_array, threshold1, threshold2) { Ok(edges) => { - let py_array = PyArray3::from_owned_array_bound(py, edges); + let py_array = PyArray3::from_owned_array(py, edges); Ok(py_array) } Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!( @@ -1317,7 +1317,7 @@ pub fn resize_py<'py>( let src_array = src.as_array(); match resize(&src_array, dsize, interp) { Ok(resized) => { - let py_array = PyArray3::from_owned_array_bound(py, resized); + let py_array = PyArray3::from_owned_array(py, resized); Ok(py_array) } Err(e) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!( @@ -1328,7 +1328,7 @@ pub fn resize_py<'py>( } #[cfg(feature = "python-bindings")] -#[pyclass] +#[pyclass(unsendable)] pub struct PyVideoCapture { inner: VideoCapture, temp_path: Option, @@ -1419,7 +1419,7 @@ impl PyVideoCapture { fn read<'py>(&mut self, py: Python<'py>) -> PyResult<(bool, Option>>)> { let (ret, frame) = self.inner.read(); if let Some(frame_data) = frame { - let py_array = PyArray3::from_owned_array_bound(py, frame_data); + let py_array = PyArray3::from_owned_array(py, frame_data); Ok((ret, Some(py_array))) } else { Ok((ret, None)) @@ -1639,7 +1639,7 @@ impl PyBatchProcessor { Ok(results) => { let py_results: Vec<_> = results .into_iter() - .map(|result| PyArray3::from_owned_array_bound(py, result)) + .map(|result| PyArray3::from_owned_array(py, result)) .collect(); Ok(py_results) } @@ -1679,7 +1679,7 @@ impl PyBatchProcessor { Ok(results) => { let py_results: Vec<_> = results .into_iter() - .map(|result| PyArray3::from_owned_array_bound(py, result)) + .map(|result| PyArray3::from_owned_array(py, result)) .collect(); Ok(py_results) } @@ -1704,7 +1704,7 @@ impl PyBatchProcessor { Ok(results) => { let py_results: Vec<_> = results .into_iter() - .map(|result| PyArray3::from_owned_array_bound(py, result)) + .map(|result| PyArray3::from_owned_array(py, result)) .collect(); Ok(py_results) } @@ -1782,7 +1782,7 @@ impl PyBatchProcessor { Ok(results) => { let py_results: Vec<_> = results .into_iter() - .map(|result| PyArray3::from_owned_array_bound(py, result)) + .map(|result| PyArray3::from_owned_array(py, result)) .collect(); Ok(py_results) } @@ -1903,7 +1903,7 @@ impl PyTrueBatchProcessor { Ok(results) => { let py_results: Vec<_> = results .into_iter() - .map(|result| PyArray3::from_owned_array_bound(py, result)) + .map(|result| PyArray3::from_owned_array(py, result)) .collect(); Ok(py_results) } @@ -1939,7 +1939,7 @@ impl PyTrueBatchProcessor { Ok(results) => { let py_results: Vec<_> = results .into_iter() - .map(|result| PyArray3::from_owned_array_bound(py, result)) + .map(|result| PyArray3::from_owned_array(py, result)) .collect(); Ok(py_results) } From 91af2343dbd255bc0f82de485dfd192efa853eb6 Mon Sep 17 00:00:00 2001 From: bghira Date: Mon, 24 Aug 2026 17:17:33 -0600 Subject: [PATCH 2/5] Align ndarray with rust-numpy 0.27 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6e7c5ce..176b8fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ image = {version = "0.25", features = ["png", "jpeg", "webp"]} imageproc = "0.25" # Metal GPU acceleration metal = {version = "0.27", optional = true} -ndarray = "0.16" +ndarray = "0.17" num_cpus = "1.16" numpy = {version = "0.27", optional = true} objc = {version = "0.2", optional = true} From 698831dfd760d157c87df3c6bb7b551b13cbd6dc Mon Sep 17 00:00:00 2001 From: bagheera <59658056+bghira@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:30:20 -0600 Subject: [PATCH 3/5] Bump version from 0.3.1 to 0.3.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 176b8fa..ce1d93f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ description = "High-performance Rust reimplementation of GIL-blocking video/imag edition = "2021" license = "MIT" name = "trainingsample" -version = "0.3.1" +version = "0.3.2" [profile.release] codegen-units = 1 From b93001768352430d9c735e3c74148e15325ae723 Mon Sep 17 00:00:00 2001 From: bagheera <59658056+bghira@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:30:41 -0600 Subject: [PATCH 4/5] Bump version from 0.3.1 to 0.3.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 36a45fd..15c46cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ description = "High-performance Rust reimplementation of GIL-blocking video/imag license = {text = "MIT"} name = "trainingsample" requires-python = ">=3.11" -version = "0.3.1" +version = "0.3.2" [project.optional-dependencies] dev = ["pytest>=6.0", "pytest-benchmark", "maturin>=1.9.5,<2.0", "pre-commit"] From 1b62f4c0de9409814f5363706730ef6db1d327f2 Mon Sep 17 00:00:00 2001 From: bghira Date: Mon, 24 Aug 2026 21:40:44 -0600 Subject: [PATCH 5/5] Fix static OpenCV builds with Homebrew --- .gitignore | 1 + docs/BUILDING_STATIC_OPENCV.md | 11 +++++++++++ scripts/build-opencv-static.sh | 16 +++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6772789..02c2c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ Cargo.lock # Static OpenCV bundle (built in CI, not committed) third_party/ opencv-build-tmp/ +ffmpeg-build-tmp/ files.tbz */config/auth.json diff --git a/docs/BUILDING_STATIC_OPENCV.md b/docs/BUILDING_STATIC_OPENCV.md index 36c177d..9284ab1 100644 --- a/docs/BUILDING_STATIC_OPENCV.md +++ b/docs/BUILDING_STATIC_OPENCV.md @@ -32,6 +32,17 @@ C and C++ compilers CI also installs clang, libclang, LLVM, nasm, and yasm before running the script. +On macOS, install the same build dependencies used by CI before running the +script: + +```bash +brew install llvm cmake nasm pkg-config +``` + +The script isolates its pinned FFmpeg build from other Homebrew libraries, so +an existing Homebrew OpenCV or FFmpeg installation does not affect the static +bundle. + ## Output | Path | Contents | diff --git a/scripts/build-opencv-static.sh b/scripts/build-opencv-static.sh index c6e08ed..ccf128a 100755 --- a/scripts/build-opencv-static.sh +++ b/scripts/build-opencv-static.sh @@ -135,7 +135,12 @@ fi # Configure CMake build echo "Configuring CMake build..." -export PKG_CONFIG_PATH="${FFMPEG_INSTALL_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH:-}" +export PKG_CONFIG_PATH="${FFMPEG_INSTALL_DIR}/lib/pkgconfig" +export PKG_CONFIG_LIBDIR="${FFMPEG_INSTALL_DIR}/lib/pkgconfig" + +# Always configure from a clean CMake cache. A failed build may leave cached +# host dependency paths behind, which can otherwise poison subsequent retries. +rm -rf "${BUILD_DIR}/build" CMAKE_ARGS=( -S "opencv-${OPENCV_VERSION}" @@ -179,6 +184,15 @@ CMAKE_ARGS=( -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" ) +# Keep Homebrew libraries from leaking into the pinned static bundle. In +# particular, OpenCV must compile against the FFmpeg headers built above, not +# a newer Homebrew FFmpeg while linking the pinned static archives. +if [[ "$(uname)" == "Darwin" ]] && command -v brew >/dev/null 2>&1; then + CMAKE_ARGS+=( + -DCMAKE_IGNORE_PREFIX_PATH="$(brew --prefix)" + ) +fi + # Disable Carotene (ARM NEON optimizations) to avoid undefined symbol errors # when statically linking. Carotene requires runtime library that isn't bundled. if [[ "$(uname)" == "Darwin" ]] || [[ "$(uname -m)" == "aarch64" ]] || [[ "$(uname -m)" == "arm64" ]]; then