From 7fc69025013a9d5452f7b889c47da754b58267b9 Mon Sep 17 00:00:00 2001 From: Will Stuckey Date: Sat, 7 Nov 2026 18:24:03 -0500 Subject: [PATCH 1/2] clean up build cache between systems/envs --- .github/workflows/ci.yml | 27 ++++++++++++++-- .gitlab-ci.yml | 2 +- CMakeLists.txt | 68 ++++++++++++++++++++++++++++++++-------- README.md | 4 +++ setup.sh | 4 +-- 5 files changed, 86 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf7fedf..8dad145 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,9 +24,6 @@ jobs: - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main - # The dev shell pins eigen_5. Ubuntu ships 3.4.0 through at least 26.04 - # LTS, which lacks Matrix::canonicalEulerAngles() -- so this cannot be an - # apt-based job without patching the source. - name: Build all targets run: | nix develop --command bash -c ' @@ -34,6 +31,30 @@ jobs: make -C build -j"$(nproc)" vision_processor geometry_benchmark blob_benchmark ' + build-apt: + name: Build (Ubuntu 24.04, apt) + # Pinned, not ubuntu-latest: 24.04 is the release target, and its packages + # (protobuf 3.21, OpenCV 4.6, ffmpeg 6.1) are far older than nixpkgs'. + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + # setup.sh's Debian package list, minus its runtime-only and Python + # packages. Keep the two in sync. + - name: Install dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends build-essential cmake pkg-config libyaml-cpp-dev \ + ocl-icd-opencl-dev libeigen3-dev libopencv-dev protobuf-compiler libprotobuf-dev \ + libavcodec-dev libavformat-dev libavutil-dev libdc1394-dev + + - name: Build all targets + run: | + cmake -B build . + make -C build -j"$(nproc)" vision_processor geometry_benchmark blob_benchmark + python: name: Python (wrapper_backend) runs-on: ubuntu-latest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9f9c1f9..3973c64 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,6 +10,6 @@ build: stage: build script: - apt -qq update - - DEBIAN_FRONTEND=noninteractive apt -y install --no-install-recommends build-essential cmake pkg-config libyaml-cpp-dev ocl-icd-opencl-dev libeigen3-dev libopencv-dev protobuf-compiler libprotobuf-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev > /dev/null + - DEBIAN_FRONTEND=noninteractive apt -y install --no-install-recommends build-essential cmake pkg-config libyaml-cpp-dev ocl-icd-opencl-dev libeigen3-dev libopencv-dev protobuf-compiler libprotobuf-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev libdc1394-dev > /dev/null - cmake -B build . - make -C build -j vision_processor diff --git a/CMakeLists.txt b/CMakeLists.txt index c4e436b..28bbb78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,12 +13,25 @@ # limitations under the License. cmake_minimum_required(VERSION 3.22) + +# A build directory belongs to one toolchain: nix develop or the distro's +# packages. CMake caches the first one's compiler and library paths and would +# otherwise silently mix them with the other's; a build from the other one +# makes a binary that only runs in the first. +option(TOOLCHAIN_CHECK "Stop when a build directory is used from the toolchain that didn't configure it" ON) +if(TOOLCHAIN_CHECK AND DEFINED CACHE{CMAKE_CXX_COMPILER}) + set(TOOLCHAIN_COMPILER "$CACHE{CMAKE_CXX_COMPILER}") + set(TOOLCHAIN_BUILD_DIR "${CMAKE_BINARY_DIR}") + include("${CMAKE_SOURCE_DIR}/cmake/CheckToolchain.cmake") +endif() + project(vision_processor) enable_language(ASM) option(WITH_SPINNAKER "Build with Spinnaker support (if library available)" ON) option(WITH_MVIMPACT "Build with mvIMPACT/Bluefox support (if library available)" ON) option(WITH_DC1394 "Build with DC1394 (IIDC FireWire) support (if library available)" ON) +option(NATIVE_ARCH "Optimize Release builds for this machine's CPU (-march=native); the binary may not run on others" ON) # Adapted from https://stackoverflow.com/a/48832234 CC BY-SA 4.0 by einpoklum if (NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") @@ -26,7 +39,10 @@ if (NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") endif() set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native -ftree-vectorize -ffast-math") +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -ftree-vectorize -ffast-math") +if(NATIVE_ARCH) + string(APPEND CMAKE_CXX_FLAGS_RELEASE " -march=native") +endif() add_compile_options("-Wall") list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -61,8 +77,21 @@ include_directories(SYSTEM ${YAML_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${SPINNAK include_directories(src) -set(PROTOC_OUT_DIR "${CMAKE_SOURCE_DIR}/src/proto") -include_directories("${PROTOC_OUT_DIR}") +# Generated code lives in the build directory, so each build directory (one per +# toolchain) has its own: protoc's output only compiles against the protobuf +# headers of the protoc that wrote it. +set(GENERATED_DIR "${CMAKE_BINARY_DIR}/generated") +set(PROTOC_OUT_DIR "${GENERATED_DIR}/proto") +include_directories("${GENERATED_DIR}" "${PROTOC_OUT_DIR}") + +# Older checkouts generated into src/, where a quoted #include from src/ would +# find these before the build directory's copies. They're gitignored output. +foreach(stale "src/proto" "src/cl_kernels.h" "src/cl_kernels.S") + if(EXISTS "${CMAKE_SOURCE_DIR}/${stale}") + file(REMOVE_RECURSE "${CMAKE_SOURCE_DIR}/${stale}") + message(STATUS "Removed generated ${stale}, now generated in ${GENERATED_DIR}") + endif() +endforeach() # league proto repository provides a generator function that fixes OUT_DIR limitations with protoc provided function list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/proto/cmake") @@ -104,25 +133,37 @@ foreach(input_file IN LISTS CL_KERNELS) endforeach() # Don't override cl_kernels.h or cl_kernels.S if already up-to-date to prevent unnecessary recompilation -if(EXISTS "${CMAKE_SOURCE_DIR}/src/cl_kernels.h") - file(READ "${CMAKE_SOURCE_DIR}/src/cl_kernels.h" CL_KERNEL_HEADER_OLD) +set(CL_KERNEL_H "${GENERATED_DIR}/cl_kernels.h") +set(CL_KERNEL_S "${GENERATED_DIR}/cl_kernels.S") + +if(EXISTS "${CL_KERNEL_H}") + file(READ "${CL_KERNEL_H}" CL_KERNEL_HEADER_OLD) endif() if(NOT "${CL_KERNEL_HEADER_OLD}" STREQUAL "${CL_KERNEL_HEADER}") - file(WRITE "${CMAKE_SOURCE_DIR}/src/cl_kernels.h" "${CL_KERNEL_HEADER}") + file(WRITE "${CL_KERNEL_H}" "${CL_KERNEL_HEADER}") message(STATUS "(Re-)Generated cl_kernels.h") endif() -if(EXISTS "src/cl_kernels.S") - file(READ "src/cl_kernels.S" CL_KERNEL_ASM_OLD) +if(EXISTS "${CL_KERNEL_S}") + file(READ "${CL_KERNEL_S}" CL_KERNEL_ASM_OLD) endif() if(NOT "${CL_KERNEL_ASM_OLD}" STREQUAL "${CL_KERNEL_ASM}") - file(WRITE "src/cl_kernels.S" "${CL_KERNEL_ASM}") + file(WRITE "${CL_KERNEL_S}" "${CL_KERNEL_ASM}") message(STATUS "(Re-)Generated cl_kernels.S") endif() -add_custom_target(AUTOGENERATE DEPENDS "src/cl_kernels.S" ${PROTO_SRCS} ${PROTO_HDRS}) -set_property(SOURCE "src/cl_kernels.S" APPEND PROPERTY OBJECT_DEPENDS ${CL_KERNEL_PATHS}) -list(APPEND SRC ${PROTO_SRCS} "src/cl_kernels.S") +add_custom_target(AUTOGENERATE DEPENDS "${CL_KERNEL_S}" ${PROTO_SRCS} ${PROTO_HDRS}) + +# No outputs, so it runs on every build, before anything is generated or compiled. +if(TOOLCHAIN_CHECK) + add_custom_target(CHECK_TOOLCHAIN + COMMAND ${CMAKE_COMMAND} "-DTOOLCHAIN_COMPILER=${CMAKE_CXX_COMPILER}" "-DTOOLCHAIN_BUILD_DIR=${CMAKE_BINARY_DIR}" + -P "${CMAKE_SOURCE_DIR}/cmake/CheckToolchain.cmake" + VERBATIM) + add_dependencies(AUTOGENERATE CHECK_TOOLCHAIN) +endif() +set_property(SOURCE "${CL_KERNEL_S}" APPEND PROPERTY OBJECT_DEPENDS ${CL_KERNEL_PATHS}) +list(APPEND SRC ${PROTO_SRCS} "${CL_KERNEL_S}") add_executable(${PROJECT_NAME} ${SRC} "src/main.cpp") add_executable("geometry_benchmark" ${SRC} "src/geometry_benchmark.cpp") @@ -137,4 +178,5 @@ if (SPINNAKER_LIBS) add_executable("spinnaker_probe" "tools/spinnaker_probe.cpp") endif () -install(TARGETS ${PROJECT_NAME} DESTINATION /usr/local/bin) +include(GNUInstallDirs) +install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/README.md b/README.md index 1811e5a..9318d02 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Installation with PIP: `pip install protobuf pyyaml` - OpenCV - protobuf - yaml-cpp + - libdc1394 (optional, for FireWire cameras) 3. Compile vision_processor: @@ -61,6 +62,9 @@ Installation with PIP: `pip install protobuf pyyaml` cmake -B build . make -C build vision_processor + The same works inside `nix develop` (see `flake.nix`). A build directory belongs to the toolchain that configured + it: to switch between nix and the distro packages, delete `build` first, or CMake stops with an error. + ## Setup diff --git a/setup.sh b/setup.sh index 74c941c..3cf1825 100755 --- a/setup.sh +++ b/setup.sh @@ -59,12 +59,12 @@ fi # Determine distribution/package manager if [[ -f /etc/arch-release ]]; then echo -e "\e[92m » Arch based distribution\e[39m" - packages="gcc make cmake pkgconf eigen opencv yaml-cpp opencl-clhpp python-protobuf python-yaml $opencl_arch" + packages="gcc make cmake pkgconf eigen opencv yaml-cpp opencl-clhpp libdc1394 python-protobuf python-yaml $opencl_arch" check_cmd="pacman -Qi $packages" install_cmd="sudo pacman -S $packages" elif [[ -f /etc/debian_version ]]; then echo -e "\e[92m » Debian based distribution\e[39m" - packages="build-essential cmake pkg-config libyaml-cpp-dev ocl-icd-opencl-dev libeigen3-dev libopencv-dev protobuf-compiler libprotobuf-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev python3-protobuf python3-yaml $opencl_debian" + packages="build-essential cmake pkg-config libyaml-cpp-dev ocl-icd-opencl-dev libeigen3-dev libopencv-dev protobuf-compiler libprotobuf-dev ffmpeg libavcodec-dev libavformat-dev libavutil-dev libdc1394-dev python3-protobuf python3-yaml $opencl_debian" check_cmd="dpkg -s $packages" install_cmd="sudo apt install --no-install-recommends $packages" else From 1d40563d7a96c758f92076da68cfb5418eb5f037 Mon Sep 17 00:00:00 2001 From: Will Stuckey Date: Sat, 7 Nov 2026 18:29:39 -0500 Subject: [PATCH 2/2] add the actual toolchain check function --- cmake/CheckToolchain.cmake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 cmake/CheckToolchain.cmake diff --git a/cmake/CheckToolchain.cmake b/cmake/CheckToolchain.cmake new file mode 100644 index 0000000..730061d --- /dev/null +++ b/cmake/CheckToolchain.cmake @@ -0,0 +1,16 @@ +# Stops a build directory being used from the toolchain that didn't configure +# it: nix develop's or the distro's packages. CMakeLists.txt includes this when +# reconfiguring, and runs it with cmake -P before every build. +# +# Expects TOOLCHAIN_COMPILER (the build directory's C++ compiler) and +# TOOLCHAIN_BUILD_DIR. + +string(FIND "${TOOLCHAIN_COMPILER}" "/nix/store/" _toolchain_nix_compiler) + +if(DEFINED ENV{IN_NIX_SHELL} AND _toolchain_nix_compiler EQUAL -1) + message(FATAL_ERROR "You're in nix develop, but ${TOOLCHAIN_BUILD_DIR} was configured outside it. " + "Leave nix develop to build it, or delete it and configure it again here.") +elseif(NOT DEFINED ENV{IN_NIX_SHELL} AND NOT _toolchain_nix_compiler EQUAL -1) + message(FATAL_ERROR "You're not in nix develop, but ${TOOLCHAIN_BUILD_DIR} was configured inside it. " + "Enter nix develop to build it, or delete it and configure it again here.") +endif()