From f5b8d08037d136826b168321561515a96142181c Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 7 Jul 2025 12:40:04 +0200 Subject: [PATCH 01/53] disable mimalloc --- apps/roofer-app/CMakeLists.txt | 5 -- apps/roofer-app/allocators.hpp | 128 ++------------------------------- apps/roofer-app/roofer-app.cpp | 5 +- vcpkg.json | 4 -- 4 files changed, 7 insertions(+), 135 deletions(-) diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index 59d24c7a..528e5a82 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -3,11 +3,6 @@ if(RF_BUILD_APPS) set(ROOFER_LINK_LIBRARIES roofer-extra fmt::fmt cmake_git_version_tracking) - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") - find_package(mimalloc CONFIG REQUIRED) - list(APPEND ROOFER_LINK_LIBRARIES $,mimalloc-static,mimalloc>) - endif() - add_executable("roofer" ${APP_SOURCES}) if(RF_USE_RERUN) diff --git a/apps/roofer-app/allocators.hpp b/apps/roofer-app/allocators.hpp index 5bf4ed60..efde1699 100644 --- a/apps/roofer-app/allocators.hpp +++ b/apps/roofer-app/allocators.hpp @@ -12,134 +12,18 @@ namespace { }; HeapAllocationCounter heap_allocation_counter; } // namespace -#endif - -/* - * Code snippet below is taken from - * https://github.com/microsoft/mimalloc/blob/dev/include/mimalloc-new-delete.h - * and modified to work with the roofer trace feature for heap memory usage. - */ -#if defined(IS_LINUX) || defined(IS_MACOS) -#if defined(_MSC_VER) && defined(_Ret_notnull_) && \ - defined(_Post_writable_byte_size_) - // stay consistent with VCRT definitions -#define mi_decl_new(n) \ - mi_decl_nodiscard mi_decl_restrict _Ret_notnull_ _Post_writable_byte_size_(n) -#define mi_decl_new_nothrow(n) \ - mi_decl_nodiscard mi_decl_restrict _Ret_maybenull_ _Success_(return != NULL) \ - _Post_writable_byte_size_(n) -#else -#define mi_decl_new(n) mi_decl_nodiscard mi_decl_restrict -#define mi_decl_new_nothrow(n) mi_decl_nodiscard mi_decl_restrict -#endif - -void operator delete(void* p) noexcept { mi_free(p); }; -void operator delete[](void* p) noexcept { mi_free(p); }; -void operator delete(void* p, const std::nothrow_t&) noexcept { mi_free(p); } -void operator delete[](void* p, const std::nothrow_t&) noexcept { mi_free(p); } - -mi_decl_new(n) void* operator new(std::size_t n) noexcept(false) { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new(n); -} -mi_decl_new(n) void* operator new[](std::size_t n) noexcept(false) { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new(n); +void* operator new(size_t size) { + heap_allocation_counter.total_allocated += size; + return malloc(size); } - -mi_decl_new_nothrow(n) void* operator new(std::size_t n, - const std::nothrow_t& tag) noexcept { - (void)(tag); -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new_nothrow(n); -} -mi_decl_new_nothrow(n) void* operator new[]( - std::size_t n, const std::nothrow_t& tag) noexcept { - (void)(tag); -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new_nothrow(n); -} - -#if (__cplusplus >= 201402L || _MSC_VER >= 1916) -void operator delete(void* p, std::size_t n) noexcept { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_freed += n; -#endif - mi_free_size(p, n); -}; -void operator delete[](void* p, std::size_t n) noexcept { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_freed += n; -#endif - mi_free_size(p, n); +void operator delete(void* memory, size_t size) noexcept { + heap_allocation_counter.total_freed += size; + free(memory); }; -#endif -#if (__cplusplus > 201402L || defined(__cpp_aligned_new)) -void operator delete(void* p, std::align_val_t al) noexcept { - mi_free_aligned(p, static_cast(al)); -} -void operator delete[](void* p, std::align_val_t al) noexcept { - mi_free_aligned(p, static_cast(al)); -} -void operator delete(void* p, std::size_t n, std::align_val_t al) noexcept { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_freed += n; #endif - mi_free_size_aligned(p, n, static_cast(al)); -}; -void operator delete[](void* p, std::size_t n, std::align_val_t al) noexcept { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_freed += n; -#endif - mi_free_size_aligned(p, n, static_cast(al)); -}; -void operator delete(void* p, std::align_val_t al, - const std::nothrow_t&) noexcept { - mi_free_aligned(p, static_cast(al)); -} -void operator delete[](void* p, std::align_val_t al, - const std::nothrow_t&) noexcept { - mi_free_aligned(p, static_cast(al)); -} -void* operator new(std::size_t n, std::align_val_t al) noexcept(false) { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new_aligned(n, static_cast(al)); -} -void* operator new[](std::size_t n, std::align_val_t al) noexcept(false) { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new_aligned(n, static_cast(al)); -} -void* operator new(std::size_t n, std::align_val_t al, - const std::nothrow_t&) noexcept { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new_aligned_nothrow(n, static_cast(al)); -} -void* operator new[](std::size_t n, std::align_val_t al, - const std::nothrow_t&) noexcept { -#ifdef RF_ENABLE_HEAP_TRACING - heap_allocation_counter.total_allocated += n; -#endif - return mi_new_aligned_nothrow(n, static_cast(al)); -} -#endif -#endif /* * Author: David Robert Nadeau * Site: http://NadeauSoftware.com/ diff --git a/apps/roofer-app/roofer-app.cpp b/apps/roofer-app/roofer-app.cpp index a0541c7f..e1ebec3e 100644 --- a/apps/roofer-app/roofer-app.cpp +++ b/apps/roofer-app/roofer-app.cpp @@ -80,10 +80,7 @@ namespace fs = std::filesystem; #include #endif -#if defined(IS_LINUX) || defined(IS_MACOS) -#include -#include -#else +#if defined(IS_WINDOWS) #undef RF_ENABLE_HEAP_TRACING #endif #include "allocators.hpp" diff --git a/vcpkg.json b/vcpkg.json index c460e7bb..8ccd5244 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -27,10 +27,6 @@ "dependencies": [ "geos", "lastools", - { - "name": "mimalloc", - "features": [] - }, { "name": "gdal", "default-features": false, From e0215129ed7591220aa2914ec03f6503c0fd072d Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 6 Aug 2025 16:35:45 +0200 Subject: [PATCH 02/53] enable a pure nix build --- CMakePresets.json | 13 + apps/external/BS_thread_pool.hpp | 2870 ++++++++++++++++++++++++++++++ apps/roofer-app/CMakeLists.txt | 2 +- flake.lock | 12 +- flake.nix | 36 +- 5 files changed, 2905 insertions(+), 28 deletions(-) create mode 100644 apps/external/BS_thread_pool.hpp diff --git a/CMakePresets.json b/CMakePresets.json index 784d557f..5fd59049 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,6 +1,19 @@ { "version": 6, "configurePresets": [ + { + "name": "nix-minimal", + "description": "Build with pure nix just the apps", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "RF_USE_VAL3DITY": "OFF", + "RF_USE_RERUN": "OFF", + "RF_USE_LOGGER_SPDLOG": "OFF", + "RF_BUILD_TESTING": "OFF", + "RF_BUILD_APPS": "ON", + "RF_BUILD_BINDINGS": "OFF" + } + }, { "name": "vcpkg-full-test", "description": "Build with vcpkg with all dependencies, to test a full build.", diff --git a/apps/external/BS_thread_pool.hpp b/apps/external/BS_thread_pool.hpp new file mode 100644 index 00000000..636b6c3a --- /dev/null +++ b/apps/external/BS_thread_pool.hpp @@ -0,0 +1,2870 @@ +/** + * ██████ ███████ ████████ ██ ██ ██████ ███████ █████ ██████ ██████ + * ██████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██████ ███████ ██ + * ███████ ██████ █████ ███████ ██ ██ ██████ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + * ██ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ ███████ ██ ██ + * ██████ ███████ ██ ██████ ██████ ███████ + * + * @file BS_thread_pool.hpp + * @author Barak Shoshany (baraksh@gmail.com) (https://baraksh.com/) + * @version 5.0.0 + * @date 2024-12-19 + * @copyright Copyright (c) 2024 Barak Shoshany. Licensed under the MIT license. + * If you found this project useful, please consider starring it on GitHub! If + * you use this library in software of any kind, please provide a link to the + * GitHub repository https://github.com/bshoshany/thread-pool in the source code + * and documentation. If you use this library in published research, please cite + * it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance + * Scientific Computing", doi:10.1016/j.softx.2024.101687, SoftwareX 26 (2024) + * 101687, arXiv:2105.00613 + * + * @brief `BS::thread_pool`: a fast, lightweight, modern, and easy-to-use + * C++17/C++20/C++23 thread pool library. This header file contains the entire + * library, and is the only file needed to use the library. + */ + +#ifndef BS_THREAD_POOL_HPP +#define BS_THREAD_POOL_HPP + +// We need to include since if we're using `import std` it will not +// define any feature-test macros, including `__cpp_lib_modules`, which we need +// to check if `import std` is supported in the first place. +#ifdef __has_include +#if __has_include() +#include // NOLINT(misc-include-cleaner) +#endif +#endif + +// If the macro `BS_THREAD_POOL_IMPORT_STD` is defined, import the C++ Standard +// Library as a module. Otherwise, include the relevant Standard Library header +// files. This is currently only officially supported by MSVC with Microsoft STL +// and LLVM Clang (NOT Apple Clang) with LLVM libc++. It is not supported by GCC +// with any standard library, or any compiler with GNU libstdc++. We also check +// that the feature is enabled by checking `__cpp_lib_modules`. However, MSVC +// defines this macro even in C++20 mode, which is not standards-compliant, so +// we check that we are in C++23 mode; MSVC currently reports `__cplusplus` as +// `202004L` for C++23 mode, so we use that value. +#if defined(BS_THREAD_POOL_IMPORT_STD) && defined(__cpp_lib_modules) && \ + (__cplusplus >= 202004L) && \ + (defined(_MSC_VER) || (defined(__clang__) && defined(_LIBCPP_VERSION) && \ + !defined(__apple_build_version__))) +// Only allow importing the `std` module if the library itself is imported as a +// module. If the library is included as a header file, this will force the +// program that included the header file to also import `std`, which is not +// desirable and can lead to compilation errors if the program `#include`s any +// Standard Library header files. +#ifdef BS_THREAD_POOL_MODULE +import std; +#else +#error \ + "The thread pool library cannot import the C++ Standard Library as a module using `import std` if the library itself is not imported as a module. Either use `import BS.thread_pool` to import the libary, or remove the `BS_THREAD_POOL_IMPORT_STD` macro. Aborting compilation." +#endif +#else +#undef BS_THREAD_POOL_IMPORT_STD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_concepts +#include +#endif +#ifdef __cpp_exceptions +#include +#include +#endif +#ifdef __cpp_impl_three_way_comparison +#include +#endif +#ifdef __cpp_lib_int_pow2 +#include +#endif +#ifdef __cpp_lib_semaphore +#include +#endif +#ifdef __cpp_lib_jthread +#include +#endif +#endif + +#ifdef BS_THREAD_POOL_NATIVE_EXTENSIONS +#if defined(_WIN32) +#include +#undef min +#undef max +#elif defined(__linux__) || defined(__APPLE__) +#include +#include +#include +#include +#if defined(__linux__) +#include +#include +#endif +#else +#undef BS_THREAD_POOL_NATIVE_EXTENSIONS +#endif +#endif + +#if defined(__linux__) +// On Linux, defines macros called `major` and `minor`. We +// undefine them here so the `version` struct can work. +#ifdef major +#undef major +#endif +#ifdef minor +#undef minor +#endif +#endif + +/** + * @brief A namespace used by Barak Shoshany's projects. + */ +namespace BS { +// Macros indicating the version of the thread pool library. +#define BS_THREAD_POOL_VERSION_MAJOR 5 +#define BS_THREAD_POOL_VERSION_MINOR 0 +#define BS_THREAD_POOL_VERSION_PATCH 0 + + /** + * @brief A struct used to store a version number, which can be checked and + * compared at compilation time. + */ + struct version { + constexpr version(const std::uint64_t major_, const std::uint64_t minor_, + const std::uint64_t patch_) noexcept + : major(major_), minor(minor_), patch(patch_) {} + +// In C++20 and later we can use the spaceship operator `<=>` to automatically +// generate comparison operators. In C++17 we have to define them manually. +#ifdef __cpp_impl_three_way_comparison + std::strong_ordering operator<=>(const version&) const = default; +#else + [[nodiscard]] constexpr friend bool operator==( + const version& lhs, const version& rhs) noexcept { + return std::tuple(lhs.major, lhs.minor, lhs.patch) == + std::tuple(rhs.major, rhs.minor, rhs.patch); + } + + [[nodiscard]] constexpr friend bool operator!=( + const version& lhs, const version& rhs) noexcept { + return !(lhs == rhs); + } + + [[nodiscard]] constexpr friend bool operator<(const version& lhs, + const version& rhs) noexcept { + return std::tuple(lhs.major, lhs.minor, lhs.patch) < + std::tuple(rhs.major, rhs.minor, rhs.patch); + } + + [[nodiscard]] constexpr friend bool operator>=( + const version& lhs, const version& rhs) noexcept { + return !(lhs < rhs); + } + + [[nodiscard]] constexpr friend bool operator>(const version& lhs, + const version& rhs) noexcept { + return std::tuple(lhs.major, lhs.minor, lhs.patch) > + std::tuple(rhs.major, rhs.minor, rhs.patch); + } + + [[nodiscard]] constexpr friend bool operator<=( + const version& lhs, const version& rhs) noexcept { + return !(lhs > rhs); + } +#endif + + [[nodiscard]] std::string to_string() const { + return std::to_string(major) + '.' + std::to_string(minor) + '.' + + std::to_string(patch); + } + + friend std::ostream& operator<<(std::ostream& stream, const version& ver) { + stream << ver.to_string(); + return stream; + } + + std::uint64_t major; + std::uint64_t minor; + std::uint64_t patch; + }; // struct version + + /** + * @brief The version of the thread pool library. + */ + inline constexpr version thread_pool_version(BS_THREAD_POOL_VERSION_MAJOR, + BS_THREAD_POOL_VERSION_MINOR, + BS_THREAD_POOL_VERSION_PATCH); + +#ifdef BS_THREAD_POOL_MODULE + // If the library is being compiled as a module, ensure that the version of + // the module file matches the version of the header file. + static_assert(thread_pool_version == version(BS_THREAD_POOL_MODULE), + "The versions of BS.thread_pool.cppm and BS_thread_pool.hpp do " + "not match. Aborting compilation."); + /** + * @brief A flag indicating whether the thread pool library was compiled as a + * C++20 module. + */ + inline constexpr bool thread_pool_module = true; +#else + /** + * @brief A flag indicating whether the thread pool library was compiled as a + * C++20 module. + */ + inline constexpr bool thread_pool_module = false; +#endif + +#ifdef BS_THREAD_POOL_IMPORT_STD + /** + * @brief A flag indicating whether the thread pool library imported the C++23 + * Standard Library module using `import std`. + */ + inline constexpr bool thread_pool_import_std = true; +#else + /** + * @brief A flag indicating whether the thread pool library imported the C++23 + * Standard Library module using `import std`. + */ + inline constexpr bool thread_pool_import_std = false; +#endif + +#ifdef BS_THREAD_POOL_NATIVE_EXTENSIONS + /** + * @brief A flag indicating whether the thread pool library's native + * extensions are enabled. + */ + inline constexpr bool thread_pool_native_extensions = true; +#else + /** + * @brief A flag indicating whether the thread pool library's native + * extensions are enabled. + */ + inline constexpr bool thread_pool_native_extensions = false; +#endif + + /** + * @brief The type used for the bitmask template parameter of the thread pool. + */ + using opt_t = std::uint8_t; + + template + class thread_pool; + +#ifdef __cpp_lib_move_only_function + /** + * @brief The template to use to store functions in the task queue and other + * places. In C++23 and later we use `std::move_only_function`. + */ + template + using function_t = std::move_only_function; +#else + /** + * @brief The template to use to store functions in the task queue and other + * places. In C++17 we use `std::function`. + */ + template + using function_t = std::function; +#endif + + /** + * @brief The type of tasks in the task queue. + */ + using task_t = function_t; + +#ifdef __cpp_lib_jthread + /** + * @brief The type of threads to use. In C++20 and later we use + * `std::jthread`. + */ + using thread_t = std::jthread; +// The following macros are used to determine how to stop the workers. In C++20 +// and later we can use `std::stop_token`. +#define BS_THREAD_POOL_WORKER_TOKEN const std::stop_token &stop_token, +#define BS_THREAD_POOL_WAIT_TOKEN , stop_token +#define BS_THREAD_POOL_STOP_CONDITION stop_token.stop_requested() +#define BS_THREAD_POOL_OR_STOP_CONDITION +#else + /** + * @brief The type of threads to use. In C++17 we use`std::thread`. + */ + using thread_t = std::thread; +// The following macros are used to determine how to stop the workers. In C++17 +// we use a manual flag `workers_running`. +#define BS_THREAD_POOL_WORKER_TOKEN +#define BS_THREAD_POOL_WAIT_TOKEN +#define BS_THREAD_POOL_STOP_CONDITION !workers_running +#define BS_THREAD_POOL_OR_STOP_CONDITION || !workers_running +#endif + + /** + * @brief A type used to indicate the priority of a task. Defined to be a + * signed integer with a width of exactly 8 bits (-128 to +127). + */ + using priority_t = std::int8_t; + + /** + * @brief An enum containing some pre-defined priorities for convenience. + */ + enum pr : priority_t { + lowest = -128, + low = -64, + normal = 0, + high = +64, + highest = +127 + }; + + /** + * @brief A helper struct to store a task with an assigned priority. + */ + struct [[nodiscard]] pr_task { + /** + * @brief Construct a new task with an assigned priority. + * + * @param task_ The task. + * @param priority_ The desired priority. + */ + explicit pr_task(task_t&& task_, const priority_t priority_ = 0) noexcept( + std::is_nothrow_move_constructible_v) + : task(std::move(task_)), priority(priority_) {} + + /** + * @brief Compare the priority of two tasks. + * + * @param lhs The first task. + * @param rhs The second task. + * @return `true` if the first task has a lower priority than the second + * task, `false` otherwise. + */ + [[nodiscard]] friend bool operator<(const pr_task& lhs, + const pr_task& rhs) noexcept { + return lhs.priority < rhs.priority; + } + + /** + * @brief The task. + */ + task_t task; + + /** + * @brief The priority of the task. + */ + priority_t priority = 0; + }; // struct pr_task + +// In C++20 and later we can use concepts. In C++17 we instead use SFINAE +// ("Substitution Failure Is Not An Error") with `std::enable_if_t`. +#ifdef __cpp_concepts +#define BS_THREAD_POOL_IF_PAUSE_ENABLED \ + template \ + requires(P) + template + concept init_func_c = std::invocable || std::invocable; +#define BS_THREAD_POOL_INIT_FUNC_CONCEPT(F) init_func_c F +#else +#define BS_THREAD_POOL_IF_PAUSE_ENABLED \ + template > +#define BS_THREAD_POOL_INIT_FUNC_CONCEPT(F) \ + typename F, \ + typename = std::enable_if_t < std::is_invocable_v || \ + std::is_invocable_v < F, \ + std::size_t >> // NOLINT(bugprone-macro-parentheses) +#endif + + /** + * @brief A helper class to facilitate waiting for and/or getting the results + * of multiple futures at once. + * + * @tparam T The return type of the futures. + */ + template + class [[nodiscard]] multi_future : public std::vector> { + public: + // Inherit all constructors from the base class `std::vector`. + using std::vector>::vector; + + /** + * @brief Get the results from all the futures stored in this + * `BS::multi_future`, rethrowing any stored exceptions. + * + * @return If the futures return `void`, this function returns `void` as + * well. Otherwise, it returns a vector containing the results. + */ + [[nodiscard]] std::conditional_t, void, std::vector> + get() { + if constexpr (std::is_void_v) { + for (std::future& future : *this) future.get(); + return; + } else { + std::vector results; + results.reserve(this->size()); + for (std::future& future : *this) results.push_back(future.get()); + return results; + } + } + + /** + * @brief Check how many of the futures stored in this `BS::multi_future` + * are ready. + * + * @return The number of ready futures. + */ + [[nodiscard]] std::size_t ready_count() const { + std::size_t count = 0; + for (const std::future& future : *this) { + if (future.wait_for(std::chrono::duration::zero()) == + std::future_status::ready) + ++count; + } + return count; + } + + /** + * @brief Check if all the futures stored in this `BS::multi_future` are + * valid. + * + * @return `true` if all futures are valid, `false` if at least one of the + * futures is not valid. + */ + [[nodiscard]] bool valid() const noexcept { + bool is_valid = true; + for (const std::future& future : *this) + is_valid = is_valid && future.valid(); + return is_valid; + } + + /** + * @brief Wait for all the futures stored in this `BS::multi_future`. + */ + void wait() const { + for (const std::future& future : *this) future.wait(); + } + + /** + * @brief Wait for all the futures stored in this `BS::multi_future`, but + * stop waiting after the specified duration has passed. This function first + * waits for the first future for the desired duration. If that future is + * ready before the duration expires, this function waits for the second + * future for whatever remains of the duration. It continues similarly until + * the duration expires. + * + * @tparam R An arithmetic type representing the number of ticks to wait. + * @tparam P An `std::ratio` representing the length of each tick in + * seconds. + * @param duration The amount of time to wait. + * @return `true` if all futures have been waited for before the duration + * expired, `false` otherwise. + */ + template + bool wait_for(const std::chrono::duration& duration) const { + const std::chrono::time_point start_time = + std::chrono::steady_clock::now(); + for (const std::future& future : *this) { + future.wait_for(duration - + (std::chrono::steady_clock::now() - start_time)); + if (duration < std::chrono::steady_clock::now() - start_time) + return false; + } + return true; + } + + /** + * @brief Wait for all the futures stored in this `BS::multi_future`, but + * stop waiting after the specified time point has been reached. This + * function first waits for the first future until the desired time point. + * If that future is ready before the time point is reached, this function + * waits for the second future until the desired time point. It continues + * similarly until the time point is reached. + * + * @tparam C The type of the clock used to measure time. + * @tparam D An `std::chrono::duration` type used to indicate the time + * point. + * @param timeout_time The time point at which to stop waiting. + * @return `true` if all futures have been waited for before the time point + * was reached, `false` otherwise. + */ + template + bool wait_until(const std::chrono::time_point& timeout_time) const { + for (const std::future& future : *this) { + future.wait_until(timeout_time); + if (timeout_time < std::chrono::steady_clock::now()) return false; + } + return true; + } + }; // class multi_future + + /** + * @brief A helper class to divide a range into blocks. Used by + * `detach_blocks()`, `submit_blocks()`, `detach_loop()`, and `submit_loop()`. + * + * @tparam T The type of the indices. Should be a signed or unsigned integer. + */ + template + class [[nodiscard]] blocks { + public: + /** + * @brief Construct a `blocks` object with the given specifications. + * + * @param first_index_ The first index in the range. + * @param index_after_last_ The index after the last index in the range. + * @param num_blocks_ The desired number of blocks to divide the range into. + */ + blocks(const T first_index_, const T index_after_last_, + const std::size_t num_blocks_) noexcept + : first_index(first_index_), + index_after_last(index_after_last_), + num_blocks(num_blocks_) { + if (index_after_last > first_index) { + const std::size_t total_size = + static_cast(index_after_last - first_index); + num_blocks = std::min(num_blocks, total_size); + block_size = total_size / num_blocks; + remainder = total_size % num_blocks; + if (block_size == 0) { + block_size = 1; + num_blocks = (total_size > 1) ? total_size : 1; + } + } else { + num_blocks = 0; + } + } + + /** + * @brief Get the index after the last index of a block. + * + * @param block The block number. + * @return The index after the last index. + */ + [[nodiscard]] T end(const std::size_t block) const noexcept { + return (block == num_blocks - 1) ? index_after_last : start(block + 1); + } + + /** + * @brief Get the number of blocks. Note that this may be different than the + * desired number of blocks that was passed to the constructor. + * + * @return The number of blocks. + */ + [[nodiscard]] std::size_t get_num_blocks() const noexcept { + return num_blocks; + } + + /** + * @brief Get the first index of a block. + * + * @param block The block number. + * @return The first index. + */ + [[nodiscard]] T start(const std::size_t block) const noexcept { + return first_index + static_cast(block * block_size) + + static_cast(block < remainder ? block : remainder); + } + + private: + /** + * @brief The size of each block (except possibly the last block). + */ + std::size_t block_size = 0; + + /** + * @brief The first index in the range. + */ + T first_index = 0; + + /** + * @brief The index after the last index in the range. + */ + T index_after_last = 0; + + /** + * @brief The number of blocks. + */ + std::size_t num_blocks = 0; + + /** + * @brief The remainder obtained after dividing the total size by the number + * of blocks. + */ + std::size_t remainder = 0; + }; // class blocks + +#ifdef __cpp_exceptions + /** + * @brief An exception that will be thrown by `wait()`, `wait_for()`, and + * `wait_until()` if the user tries to call them from within a thread of the + * same pool, which would result in a deadlock. Only used if the flag + * `BS:tp::wait_deadlock_checks` is enabled in the template parameter of + * `BS::thread_pool`. + */ + struct wait_deadlock : public std::runtime_error { + wait_deadlock() : std::runtime_error("BS::wait_deadlock"){}; + }; +#endif + +#ifdef BS_THREAD_POOL_NATIVE_EXTENSIONS +#if defined(_WIN32) + /** + * @brief An enum containing pre-defined OS-specific process priority values + * for portability. + */ + enum class os_process_priority { + idle = IDLE_PRIORITY_CLASS, + below_normal = BELOW_NORMAL_PRIORITY_CLASS, + normal = NORMAL_PRIORITY_CLASS, + above_normal = ABOVE_NORMAL_PRIORITY_CLASS, + high = HIGH_PRIORITY_CLASS, + realtime = REALTIME_PRIORITY_CLASS + }; + + /** + * @brief An enum containing pre-defined OS-specific thread priority values + * for portability. + */ + enum class os_thread_priority { + idle = THREAD_PRIORITY_IDLE, + lowest = THREAD_PRIORITY_LOWEST, + below_normal = THREAD_PRIORITY_BELOW_NORMAL, + normal = THREAD_PRIORITY_NORMAL, + above_normal = THREAD_PRIORITY_ABOVE_NORMAL, + highest = THREAD_PRIORITY_HIGHEST, + realtime = THREAD_PRIORITY_TIME_CRITICAL + }; +#elif defined(__linux__) || defined(__APPLE__) + /** + * @brief An enum containing pre-defined OS-specific process priority values + * for portability. + */ + enum class os_process_priority { + idle = PRIO_MAX - 2, + below_normal = PRIO_MAX / 2, + normal = 0, + above_normal = PRIO_MIN / 3, + high = PRIO_MIN * 2 / 3, + realtime = PRIO_MIN + }; + + /** + * @brief An enum containing pre-defined OS-specific thread priority values + * for portability. + */ + enum class os_thread_priority { + idle, + lowest, + below_normal, + normal, + above_normal, + highest, + realtime + }; +#endif + + /** + * @brief Get the processor affinity of the current process using the current + * platform's native API. This should work on Windows and Linux, but is not + * possible on macOS as the native API does not allow it. + * + * @return An `std::optional` object, optionally containing the processor + * affinity of the current process as an `std::vector` where each + * element corresponds to a logical processor. If the returned object does not + * contain a value, then the affinity could not be determined. On macOS, this + * function always returns `std::nullopt`. + */ + [[nodiscard]] inline std::optional> + get_os_process_affinity() { +#if defined(_WIN32) + DWORD_PTR process_mask = 0; + DWORD_PTR system_mask = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, + &system_mask) == 0) + return std::nullopt; +#ifdef __cpp_lib_int_pow2 + const std::size_t num_cpus = + static_cast(std::bit_width(system_mask)); +#else + std::size_t num_cpus = 0; + if (system_mask != 0) { + num_cpus = 1; + while ((system_mask >>= 1U) != 0U) ++num_cpus; + } +#endif + std::vector affinity(num_cpus); + for (std::size_t i = 0; i < num_cpus; ++i) + affinity[i] = ((process_mask & (1ULL << i)) != 0ULL); + return affinity; +#elif defined(__linux__) + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + if (sched_getaffinity(getpid(), sizeof(cpu_set_t), &cpu_set) != 0) + return std::nullopt; + const int num_cpus = get_nprocs(); + if (num_cpus < 1) return std::nullopt; + std::vector affinity(static_cast(num_cpus)); + for (std::size_t i = 0; i < affinity.size(); ++i) + affinity[i] = CPU_ISSET(i, &cpu_set); + return affinity; +#elif defined(__APPLE__) + return std::nullopt; +#endif + } + + /** + * @brief Set the processor affinity of the current process using the current + * platform's native API. This should work on Windows and Linux, but is not + * possible on macOS as the native API does not allow it. + * + * @param affinity The processor affinity to set, as an `std::vector` + * where each element corresponds to a logical processor. + * @return `true` if the affinity was set successfully, `false` otherwise. On + * macOS, this function always returns `false`. + */ + inline bool set_os_process_affinity(const std::vector& affinity) { +#if defined(_WIN32) + DWORD_PTR process_mask = 0; + for (std::size_t i = 0; + i < std::min(affinity.size(), sizeof(DWORD_PTR) * 8); ++i) + process_mask |= (affinity[i] ? (1ULL << i) : 0ULL); + return SetProcessAffinityMask(GetCurrentProcess(), process_mask) != 0; +#elif defined(__linux__) + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + for (std::size_t i = 0; + i < std::min(affinity.size(), CPU_SETSIZE); ++i) { + if (affinity[i]) CPU_SET(i, &cpu_set); + } + return sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpu_set) == 0; +#elif defined(__APPLE__) + return affinity[0] && + false; // NOLINT(readability-simplify-boolean-expr) // Using + // `affinity` to suppress unused parameter warning. +#endif + } + + /** + * @brief Get the priority of the current process using the current platform's + * native API. This should work on Windows, Linux, and macOS. + * + * @return An `std::optional` object, optionally containing the priority of + * the current process, as a member of the enum `BS::os_process_priority`. If + * the returned object does not contain a value, then either the priority + * could not be determined, or it is not one of the pre-defined values and + * therefore cannot be represented in a portable way. + */ + [[nodiscard]] inline std::optional + get_os_process_priority() { +#if defined(_WIN32) + // On Windows, this is straightforward. + const DWORD priority = GetPriorityClass(GetCurrentProcess()); + if (priority == 0) return std::nullopt; + return static_cast(priority); +#elif defined(__linux__) || defined(__APPLE__) + // On Linux/macOS there is no direct analogue of `GetPriorityClass()` on + // Windows, so instead we get the "nice" value. The usual range is -20 to 19 + // or 20, with higher values corresponding to lower priorities. However, we + // are only using 6 pre-defined values for portability, so if the value was + // set via any means other than `BS::set_os_process_priority()`, it may not + // match one of our pre-defined values. Note that `getpriority()` returns -1 + // on error, but since this does not correspond to any of our pre-defined + // values, this function will return `std::nullopt` anyway. + const int nice_val = getpriority(PRIO_PROCESS, static_cast(getpid())); + switch (nice_val) { + case static_cast(os_process_priority::idle): + return os_process_priority::idle; + case static_cast(os_process_priority::below_normal): + return os_process_priority::below_normal; + case static_cast(os_process_priority::normal): + return os_process_priority::normal; + case static_cast(os_process_priority::above_normal): + return os_process_priority::above_normal; + case static_cast(os_process_priority::high): + return os_process_priority::high; + case static_cast(os_process_priority::realtime): + return os_process_priority::realtime; + default: + return std::nullopt; + } +#endif + } + + /** + * @brief Set the priority of the current process using the current platform's + * native API. This should work on Windows, Linux, and macOS. However, note + * that higher priorities might require elevated permissions. + * + * @param priority The priority to set. Must be a value from the enum + * `BS::os_process_priority`. + * @return `true` if the priority was set successfully, `false` otherwise. + * Usually, `false` means that the user does not have the necessary + * permissions to set the desired priority. + */ + inline bool set_os_process_priority(const os_process_priority priority) { +#if defined(_WIN32) + // On Windows, this is straightforward. + return SetPriorityClass(GetCurrentProcess(), + static_cast(priority)) != 0; +#elif defined(__linux__) || defined(__APPLE__) + // On Linux/macOS there is no direct analogue of `SetPriorityClass()` on + // Windows, so instead we set the "nice" value. The usual range is -20 to 19 + // or 20, with higher values corresponding to lower priorities. However, we + // are only using 6 pre-defined values for portability. Note that the "nice" + // values are only relevant for the `SCHED_OTHER` policy, but we do not set + // that policy here, as it is per-thread rather than per-process. Also, it's + // important to note that a non-root user cannot decrease the nice value + // (i.e. increase the process priority), only increase it. This can cause + // confusing behavior. For example, if the current priority is + // `BS::os_process_priority::normal` and the user sets it to + // `BS::os_process_priority::idle`, they cannot change it back + // `BS::os_process_priority::normal`. + return setpriority(PRIO_PROCESS, static_cast(getpid()), + static_cast(priority)) == 0; +#endif + } +#endif + + /** + * @brief A class used to obtain information about the current thread and, if + * native extensions are enabled, set its priority and affinity. + */ + class [[nodiscard]] this_thread { + template + friend class thread_pool; + + public: + /** + * @brief Get the index of the current thread. If this thread belongs to a + * `BS::thread_pool` object, the return value will be an index in the range + * `[0, N)` where `N == BS::thread_pool::get_thread_count()`. Otherwise, for + * example if this thread is the main thread or an independent thread not in + * any pools, `std::nullopt` will be returned. + * + * @return An `std::optional` object, optionally containing a thread index. + */ + [[nodiscard]] static std::optional get_index() noexcept { + return my_index; + } + + /** + * @brief Get a pointer to the thread pool that owns the current thread. If + * this thread belongs to a `BS::thread_pool` object, the return value will + * be a `void` pointer to that object. Otherwise, for example if this thread + * is the main thread or an independent thread not in any pools, + * `std::nullopt` will be returned. + * + * @return An `std::optional` object, optionally containing a pointer to a + * thread pool. Note that this will be a `void` pointer, so it must be cast + * to the desired instantiation of the `BS::thread_pool` template in order + * to use any member functions. + */ + [[nodiscard]] static std::optional get_pool() noexcept { + return my_pool; + } + +#ifdef BS_THREAD_POOL_NATIVE_EXTENSIONS + /** + * @brief Get the processor affinity of the current thread using the current + * platform's native API. This should work on Windows and Linux, but is not + * possible on macOS as the native API does not allow it. + * + * @return An `std::optional` object, optionally containing the processor + * affinity of the current thread as an `std::vector` where each + * element corresponds to a logical processor. If the returned object does + * not contain a value, then the affinity could not be determined. On macOS, + * this function always returns `std::nullopt`. + */ + [[nodiscard]] static std::optional> + get_os_thread_affinity() { +#if defined(_WIN32) + // Windows does not have a `GetThreadAffinityMask()` function, but + // `SetThreadAffinityMask()` returns the previous affinity mask, so we can + // use that to get the current affinity and then restore it. It's a bit of + // a hack, but it works. Since the thread affinity must be a subset of the + // process affinity, we use the process affinity as the temporary value. + DWORD_PTR process_mask = 0; + DWORD_PTR system_mask = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, + &system_mask) == 0) + return std::nullopt; + const DWORD_PTR previous_mask = + SetThreadAffinityMask(GetCurrentThread(), process_mask); + if (previous_mask == 0) return std::nullopt; + SetThreadAffinityMask(GetCurrentThread(), previous_mask); +#ifdef __cpp_lib_int_pow2 + const std::size_t num_cpus = + static_cast(std::bit_width(system_mask)); +#else + std::size_t num_cpus = 0; + if (system_mask != 0) { + num_cpus = 1; + while ((system_mask >>= 1U) != 0U) ++num_cpus; + } +#endif + std::vector affinity(num_cpus); + for (std::size_t i = 0; i < num_cpus; ++i) + affinity[i] = ((previous_mask & (1ULL << i)) != 0ULL); + return affinity; +#elif defined(__linux__) + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + if (pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_set) != + 0) + return std::nullopt; + const int num_cpus = get_nprocs(); + if (num_cpus < 1) return std::nullopt; + std::vector affinity(static_cast(num_cpus)); + for (std::size_t i = 0; i < affinity.size(); ++i) + affinity[i] = CPU_ISSET(i, &cpu_set); + return affinity; +#elif defined(__APPLE__) + return std::nullopt; +#endif + } + + /** + * @brief Set the processor affinity of the current thread using the current + * platform's native API. This should work on Windows and Linux, but is not + * possible on macOS as the native API does not allow it. Note that the + * thread affinity must be a subset of the process affinity (as obtained + * using `BS::get_os_process_affinity()`) for the containing process of a + * thread. + * + * @param affinity The processor affinity to set, as an `std::vector` + * where each element corresponds to a logical processor. + * @return `true` if the affinity was set successfully, `false` otherwise. + * On macOS, this function always returns `false`. + */ + static bool set_os_thread_affinity(const std::vector& affinity) { +#if defined(_WIN32) + DWORD_PTR thread_mask = 0; + for (std::size_t i = 0; + i < std::min(affinity.size(), sizeof(DWORD_PTR) * 8); + ++i) + thread_mask |= (affinity[i] ? (1ULL << i) : 0ULL); + return SetThreadAffinityMask(GetCurrentThread(), thread_mask) != 0; +#elif defined(__linux__) + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + for (std::size_t i = 0; + i < std::min(affinity.size(), CPU_SETSIZE); ++i) { + if (affinity[i]) CPU_SET(i, &cpu_set); + } + return pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), + &cpu_set) == 0; +#elif defined(__APPLE__) + return affinity[0] && + false; // NOLINT(readability-simplify-boolean-expr) // Using + // `affinity` to suppress unused parameter warning. +#endif + } + + /** + * @brief Get the name of the current thread using the current platform's + * native API. This should work on Windows, Linux, and macOS. + * + * @return An `std::optional` object, optionally containing the name of the + * current thread. If the returned object does not contain a value, then the + * name could not be determined. + */ + [[nodiscard]] static std::optional get_os_thread_name() { +#if defined(_WIN32) + // On Windows thread names are wide strings, so we need to convert them to + // normal strings. + PWSTR data = nullptr; + const HRESULT hr = GetThreadDescription(GetCurrentThread(), &data); + if (FAILED(hr)) return std::nullopt; + if (data == nullptr) return std::nullopt; + const int size = WideCharToMultiByte(CP_UTF8, 0, data, -1, nullptr, 0, + nullptr, nullptr); + if (size == 0) { + LocalFree(data); + return std::nullopt; + } + std::string name(static_cast(size) - 1, 0); + const int result = WideCharToMultiByte(CP_UTF8, 0, data, -1, name.data(), + size, nullptr, nullptr); + LocalFree(data); + if (result == 0) return std::nullopt; + return name; +#elif defined(__linux__) || defined(__APPLE__) +#ifdef __linux__ + // On Linux thread names are limited to 16 characters, including the null + // terminator. + constexpr std::size_t buffer_size = 16; +#else + // On macOS thread names are limited to 64 characters, including the null + // terminator. + constexpr std::size_t buffer_size = 64; +#endif + char name[buffer_size] = {}; + if (pthread_getname_np(pthread_self(), name, buffer_size) != 0) + return std::nullopt; + return std::string(name); +#endif + } + + /** + * @brief Set the name of the current thread using the current platform's + * native API. This should work on Windows, Linux, and macOS. Note that on + * Linux thread names are limited to 16 characters, including the null + * terminator. + * + * @param name The name to set. + * @return `true` if the name was set successfully, `false` otherwise. + */ + static bool set_os_thread_name(const std::string& name) { +#if defined(_WIN32) + // On Windows thread names are wide strings, so we need to convert them + // from normal strings. + const int size = + MultiByteToWideChar(CP_UTF8, 0, name.data(), -1, nullptr, 0); + if (size == 0) return false; + std::wstring wide(static_cast(size), 0); + if (MultiByteToWideChar(CP_UTF8, 0, name.data(), -1, wide.data(), size) == + 0) + return false; + const HRESULT hr = SetThreadDescription(GetCurrentThread(), wide.data()); + return SUCCEEDED(hr); +#elif defined(__linux__) + // On Linux this is straightforward. + return pthread_setname_np(pthread_self(), name.data()) == 0; +#elif defined(__APPLE__) + // On macOS, unlike Linux, a thread can only set a name for itself, so the + // signature is different. + return pthread_setname_np(name.data()) == 0; +#endif + } + + /** + * @brief Get the priority of the current thread using the current + * platform's native API. This should work on Windows, Linux, and macOS. + * + * @return An `std::optional` object, optionally containing the priority of + * the current thread, as a member of the enum `BS::os_thread_priority`. If + * the returned object does not contain a value, then either the priority + * could not be determined, or it is not one of the pre-defined values. + */ + [[nodiscard]] static std::optional + get_os_thread_priority() { +#if defined(_WIN32) + // On Windows, this is straightforward. + const int priority = GetThreadPriority(GetCurrentThread()); + if (priority == THREAD_PRIORITY_ERROR_RETURN) return std::nullopt; + return static_cast(priority); +#elif defined(__linux__) + // On Linux, we distill the choices of scheduling policy, priority, and + // "nice" value into 7 pre-defined levels, for simplicity and portability. + // The total number of possible combinations of policies and priorities is + // much larger, so if the value was set via any means other than + // `BS::this_thread::set_os_thread_priority()`, it may not match one of + // our pre-defined values. + int policy = 0; + struct sched_param param = {}; + if (pthread_getschedparam(pthread_self(), &policy, ¶m) != 0) + return std::nullopt; + if (policy == SCHED_FIFO && + param.sched_priority == sched_get_priority_max(SCHED_FIFO)) { + // The only pre-defined priority that uses SCHED_FIFO and the maximum + // available priority value is the "realtime" priority. + return os_thread_priority::realtime; + } + if (policy == SCHED_RR && + param.sched_priority == sched_get_priority_min(SCHED_RR) + + (sched_get_priority_max(SCHED_RR) - + sched_get_priority_min(SCHED_RR)) / + 2) { + // The only pre-defined priority that uses SCHED_RR and a priority in + // the middle of the available range is the "highest" priority. + return os_thread_priority::highest; + } +#ifdef __linux__ + if (policy == SCHED_IDLE) { + // The only pre-defined priority that uses SCHED_IDLE is the "idle" + // priority. Note that this scheduling policy is not available on macOS. + return os_thread_priority::idle; + } +#endif + if (policy == SCHED_OTHER) { + // For SCHED_OTHER, the result depends on the "nice" value. The usual + // range is -20 to 19 or 20, with higher values corresponding to lower + // priorities. Note that `getpriority()` returns -1 on error, but since + // this does not correspond to any of our pre-defined values, this + // function will return `std::nullopt` anyway. + const int nice_val = + getpriority(PRIO_PROCESS, static_cast(syscall(SYS_gettid))); + switch (nice_val) { + case PRIO_MIN + 2: + return os_thread_priority::above_normal; + case 0: + return os_thread_priority::normal; + case (PRIO_MAX / 2) + (PRIO_MAX % 2): + return os_thread_priority::below_normal; + case PRIO_MAX - 3: + return os_thread_priority::lowest; +#ifdef __APPLE__ + // `SCHED_IDLE` doesn't exist on macOS, so we use the policy + // `SCHED_OTHER` with a "nice" value of `PRIO_MAX - 2`. + case PRIO_MAX - 2: + return os_thread_priority::idle; +#endif + default: + return std::nullopt; + } + } + return std::nullopt; +#elif defined(__APPLE__) + // On macOS, we distill the choices of scheduling policy and priority into + // 7 pre-defined levels, for simplicity and portability. The total number + // of possible combinations of policies and priorities is much larger, so + // if the value was set via any means other than + // `BS::this_thread::set_os_thread_priority()`, it may not match one of + // our pre-defined values. + int policy = 0; + struct sched_param param = {}; + if (pthread_getschedparam(pthread_self(), &policy, ¶m) != 0) + return std::nullopt; + if (policy == SCHED_FIFO && + param.sched_priority == sched_get_priority_max(SCHED_FIFO)) { + // The only pre-defined priority that uses SCHED_FIFO and the maximum + // available priority value is the "realtime" priority. + return os_thread_priority::realtime; + } + if (policy == SCHED_RR && + param.sched_priority == sched_get_priority_min(SCHED_RR) + + (sched_get_priority_max(SCHED_RR) - + sched_get_priority_min(SCHED_RR)) / + 2) { + // The only pre-defined priority that uses SCHED_RR and a priority in + // the middle of the available range is the "highest" priority. + return os_thread_priority::highest; + } + if (policy == SCHED_OTHER) { + // For SCHED_OTHER, the result depends on the specific value of the + // priority. + if (param.sched_priority == sched_get_priority_max(SCHED_OTHER)) + return os_thread_priority::above_normal; + if (param.sched_priority == sched_get_priority_min(SCHED_OTHER) + + (sched_get_priority_max(SCHED_OTHER) - + sched_get_priority_min(SCHED_OTHER)) / + 2) + return os_thread_priority::normal; + if (param.sched_priority == sched_get_priority_min(SCHED_OTHER) + + (sched_get_priority_max(SCHED_OTHER) - + sched_get_priority_min(SCHED_OTHER)) * + 2 / 3) + return os_thread_priority::below_normal; + if (param.sched_priority == sched_get_priority_min(SCHED_OTHER) + + (sched_get_priority_max(SCHED_OTHER) - + sched_get_priority_min(SCHED_OTHER)) / + 3) + return os_thread_priority::lowest; + if (param.sched_priority == sched_get_priority_min(SCHED_OTHER)) + return os_thread_priority::idle; + return std::nullopt; + } + return std::nullopt; +#endif + } + + /** + * @brief Set the priority of the current thread using the current + * platform's native API. This should work on Windows, Linux, and macOS. + * However, note that higher priorities might require elevated permissions. + * + * @param priority The priority to set. Must be a value from the enum + * `BS::os_thread_priority`. + * @return `true` if the priority was set successfully, `false` otherwise. + * Usually, `false` means that the user does not have the necessary + * permissions to set the desired priority. + */ + static bool set_os_thread_priority(const os_thread_priority priority) { +#if defined(_WIN32) + // On Windows, this is straightforward. + return SetThreadPriority(GetCurrentThread(), + static_cast(priority)) != 0; +#elif defined(__linux__) + // On Linux, we distill the choices of scheduling policy, priority, and + // "nice" value into 7 pre-defined levels, for simplicity and portability. + // The total number of possible combinations of policies and priorities is + // much larger, but allowing more fine-grained control would not be + // portable. + int policy = 0; + struct sched_param param = {}; + std::optional nice_val = std::nullopt; + switch (priority) { + case os_thread_priority::realtime: + // "Realtime" pre-defined priority: We use the policy `SCHED_FIFO` + // with the highest possible priority. + policy = SCHED_FIFO; + param.sched_priority = sched_get_priority_max(SCHED_FIFO); + break; + case os_thread_priority::highest: + // "Highest" pre-defined priority: We use the policy `SCHED_RR` + // ("round-robin") with a priority in the middle of the available + // range. + policy = SCHED_RR; + param.sched_priority = sched_get_priority_min(SCHED_RR) + + (sched_get_priority_max(SCHED_RR) - + sched_get_priority_min(SCHED_RR)) / + 2; + break; + case os_thread_priority::above_normal: + // "Above normal" pre-defined priority: We use the policy + // `SCHED_OTHER` (the default). This policy does not accept a priority + // value, so priority must be 0. However, we set the "nice" value to + // the minimum value as given by `PRIO_MIN`, plus 2 (which should + // evaluate to -18). The usual range is -20 to 19 or 20, with higher + // values corresponding to lower priorities. + policy = SCHED_OTHER; + param.sched_priority = 0; + nice_val = PRIO_MIN + 2; + break; + case os_thread_priority::normal: + // "Normal" pre-defined priority: We use the policy `SCHED_OTHER`, + // priority must be 0, and we set the "nice" value to 0 (the default). + policy = SCHED_OTHER; + param.sched_priority = 0; + nice_val = 0; + break; + case os_thread_priority::below_normal: + // "Below normal" pre-defined priority: We use the policy + // `SCHED_OTHER`, priority must be 0, and we set the "nice" value to + // half the maximum value as given by `PRIO_MAX`, rounded up (which + // should evaluate to 10). + policy = SCHED_OTHER; + param.sched_priority = 0; + nice_val = (PRIO_MAX / 2) + (PRIO_MAX % 2); + break; + case os_thread_priority::lowest: + // "Lowest" pre-defined priority: We use the policy `SCHED_OTHER`, + // priority must be 0, and we set the "nice" value to the maximum + // value as given by `PRIO_MAX`, minus 3 (which should evaluate to + // 17). + policy = SCHED_OTHER; + param.sched_priority = 0; + nice_val = PRIO_MAX - 3; + break; + case os_thread_priority::idle: + // "Idle" pre-defined priority on Linux: We use the policy + // `SCHED_IDLE`, priority must be 0, and we don't touch the "nice" + // value. + policy = SCHED_IDLE; + param.sched_priority = 0; + break; + default: + return false; + } + bool success = + (pthread_setschedparam(pthread_self(), policy, ¶m) == 0); + if (nice_val.has_value()) + success = + success && + (setpriority(PRIO_PROCESS, static_cast(syscall(SYS_gettid)), + nice_val.value()) == 0); + return success; +#elif defined(__APPLE__) + // On macOS, unlike Linux, the "nice" value is per-process, not per-thread + // (in compliance with the POSIX standard). However, unlike Linux, + // `SCHED_OTHER` on macOS does have a range of priorities. So for + // `realtime` and `highest` priorities we use `SCHED_FIFO` and `SCHED_RR` + // respectively as for Linux, but for the other priorities we use + // `SCHED_OTHER` with a priority in the range given by + // `sched_get_priority_min(SCHED_OTHER)` to + // `sched_get_priority_max(SCHED_OTHER)`. + int policy = 0; + struct sched_param param = {}; + switch (priority) { + case os_thread_priority::realtime: + // "Realtime" pre-defined priority: We use the policy `SCHED_FIFO` + // with the highest possible priority. + policy = SCHED_FIFO; + param.sched_priority = sched_get_priority_max(SCHED_FIFO); + break; + case os_thread_priority::highest: + // "Highest" pre-defined priority: We use the policy `SCHED_RR` + // ("round-robin") with a priority in the middle of the available + // range. + policy = SCHED_RR; + param.sched_priority = sched_get_priority_min(SCHED_RR) + + (sched_get_priority_max(SCHED_RR) - + sched_get_priority_min(SCHED_RR)) / + 2; + break; + case os_thread_priority::above_normal: + // "Above normal" pre-defined priority: We use the policy + // `SCHED_OTHER` (the default) with the highest possible priority. + policy = SCHED_OTHER; + param.sched_priority = sched_get_priority_max(SCHED_OTHER); + break; + case os_thread_priority::normal: + // "Normal" pre-defined priority: We use the policy `SCHED_OTHER` (the + // default) with a priority in the middle of the available range + // (which appears to be the default?). + policy = SCHED_OTHER; + param.sched_priority = sched_get_priority_min(SCHED_OTHER) + + (sched_get_priority_max(SCHED_OTHER) - + sched_get_priority_min(SCHED_OTHER)) / + 2; + break; + case os_thread_priority::below_normal: + // "Below normal" pre-defined priority: We use the policy + // `SCHED_OTHER` (the default) with a priority equal to 2/3rds of the + // normal value. + policy = SCHED_OTHER; + param.sched_priority = sched_get_priority_min(SCHED_OTHER) + + (sched_get_priority_max(SCHED_OTHER) - + sched_get_priority_min(SCHED_OTHER)) * + 2 / 3; + break; + case os_thread_priority::lowest: + // "Lowest" pre-defined priority: We use the policy `SCHED_OTHER` (the + // default) with a priority equal to 1/3rd of the normal value. + policy = SCHED_OTHER; + param.sched_priority = sched_get_priority_min(SCHED_OTHER) + + (sched_get_priority_max(SCHED_OTHER) - + sched_get_priority_min(SCHED_OTHER)) / + 3; + break; + case os_thread_priority::idle: + // "Idle" pre-defined priority on macOS: We use the policy + // `SCHED_OTHER` (the default) with the lowest possible priority. + policy = SCHED_OTHER; + param.sched_priority = sched_get_priority_min(SCHED_OTHER); + break; + default: + return false; + } + return pthread_setschedparam(pthread_self(), policy, ¶m) == 0; +#endif + } +#endif + + private: + inline static thread_local std::optional my_index = + std::nullopt; + inline static thread_local std::optional my_pool = std::nullopt; + }; // class this_thread + + /** + * @brief A meta-programming template to determine the common type of two + * integer types. Unlike `std::common_type`, this template maintains correct + * signedness. + * + * @tparam T1 The first type. + * @tparam T2 The second type. + * @tparam Enable A dummy parameter to enable SFINAE in specializations. + */ + template + struct common_index_type { + // Fallback to `std::common_type_t` if no specialization matches. + using type = std::common_type_t; + }; + + // The common type of two signed integers is the larger of the integers, with + // the same signedness. + template + struct common_index_type< + T1, T2, std::enable_if_t && std::is_signed_v>> { + using type = std::conditional_t<(sizeof(T1) >= sizeof(T2)), T1, T2>; + }; + + // The common type of two unsigned integers is the larger of the integers, + // with the same signedness. + template + struct common_index_type< + T1, T2, + std::enable_if_t && std::is_unsigned_v>> { + using type = std::conditional_t<(sizeof(T1) >= sizeof(T2)), T1, T2>; + }; + + // The common type of a signed and an unsigned integer is a signed integer + // that can hold the full ranges of both integers. + template + struct common_index_type< + T1, T2, + std::enable_if_t<(std::is_signed_v && std::is_unsigned_v) || + (std::is_unsigned_v && std::is_signed_v)>> { + using S = std::conditional_t, T1, T2>; + using U = std::conditional_t, T1, T2>; + static constexpr std::size_t larger_size = + (sizeof(S) > sizeof(U)) ? sizeof(S) : sizeof(U); + using type = std::conditional_t< + larger_size <= 4, + // If both integers are 32 bits or less, the common type should be a + // signed type that can hold both of them. If both are 8 bits, or the + // signed type is 16 bits and the unsigned type is 8 bits, the common + // type is `std::int16_t`. Otherwise, if both are 16 bits, or the signed + // type is 32 bits and the unsigned type is smaller, the common type is + // `std::int32_t`. Otherwise, if both are 32 bits or less, the common + // type is `std::int64_t`. + std::conditional_t< + larger_size == 1 || (sizeof(S) == 2 && sizeof(U) == 1), + std::int16_t, + std::conditional_t>, + // If the unsigned integer is 64 bits, the common type should also be an + // unsigned 64-bit integer, that is, `std::uint64_t`. The reason is that + // the most common scenario where this might happen is where the indices + // go from 0 to `x` where `x` has been previously defined as + // `std::size_t`, e.g. the size of a vector. Note that this will fail if + // the first index is negative; in that case, the user must cast the + // indices explicitly to the desired common type. If the unsigned + // integer is not 64 bits, then the signed integer must be 64 bits, + // hence the common type is `std::int64_t`. + std::conditional_t>; + }; + + /** + * @brief A helper type alias to obtain the common type from the template + * `BS::common_index_type`. + * + * @tparam T1 The first type. + * @tparam T2 The second type. + */ + template + using common_index_type_t = typename common_index_type::type; + + /** + * @brief An enumeration of flags to be used in the bitmask template parameter + * of `BS::thread_pool` to enable optional features. + */ + enum tp : opt_t { + /** + * @brief No optional features enabled. + */ + none = 0, + + /** + * @brief Enable task priority. + */ + priority = 1 << 0, + + /** + * @brief Enable pausing. + */ + pause = 1 << 2, + + /** + * @brief Enable wait deadlock checks. + */ + wait_deadlock_checks = 1 << 3 + }; + + /** + * @brief A fast, lightweight, modern, and easy-to-use C++17/C++20/C++23 + * thread pool class. This alias defines a thread pool with all optional + * features disabled. + */ + using light_thread_pool = thread_pool; + + /** + * @brief A fast, lightweight, modern, and easy-to-use C++17/C++20/C++23 + * thread pool class. This alias defines a thread pool with task priority + * enabled. + */ + using priority_thread_pool = thread_pool; + + /** + * @brief A fast, lightweight, modern, and easy-to-use C++17/C++20/C++23 + * thread pool class. This alias defines a thread pool with pausing enabled. + */ + using pause_thread_pool = thread_pool; + + /** + * @brief A fast, lightweight, modern, and easy-to-use C++17/C++20/C++23 + * thread pool class. This alias defines a thread pool with wait deadlock + * checks enabled. + */ + using wdc_thread_pool = thread_pool; + + /** + * @brief A fast, lightweight, modern, and easy-to-use C++17/C++20/C++23 + * thread pool class. + * + * @tparam OptFlags A bitmask of flags which can be used to enable optional + * features. The flags are members of the `BS::tp` enumeration: + * `BS::tp::priority`, `BS::tp::pause`, and `BS::tp::wait_deadlock_checks`. + * The default is `BS::tp::none`, which disables all optional features. To + * enable multiple features, use the bitwise OR operator `|`, e.g. + * `BS::tp::priority | BS::tp::pause`. + */ + template + class [[nodiscard]] thread_pool { + public: + /** + * @brief A flag indicating whether task priority is enabled. + */ + static constexpr bool priority_enabled = (OptFlags & tp::priority) != 0; + + /** + * @brief A flag indicating whether pausing is enabled. + */ + static constexpr bool pause_enabled = (OptFlags & tp::pause) != 0; + + /** + * @brief A flag indicating whether wait deadlock checks are enabled. + */ + static constexpr bool wait_deadlock_checks_enabled = + (OptFlags & tp::wait_deadlock_checks) != 0; + +#ifndef __cpp_exceptions + static_assert(!wait_deadlock_checks_enabled, + "Wait deadlock checks cannot be enabled if exception " + "handling is disabled."); +#endif + + // ============================ + // Constructors and destructors + // ============================ + + /** + * @brief Construct a new thread pool. The number of threads will be the + * total number of hardware threads available, as reported by the + * implementation. This is usually determined by the number of cores in the + * CPU. If a core is hyperthreaded, it will count as two threads. + */ + thread_pool() : thread_pool(0, [] {}) {} + + /** + * @brief Construct a new thread pool with the specified number of threads. + * + * @param num_threads The number of threads to use. + */ + explicit thread_pool(const std::size_t num_threads) + : thread_pool(num_threads, [] {}) {} + + /** + * @brief Construct a new thread pool with the specified initialization + * function. + * + * @param init An initialization function to run in each thread before it + * starts executing any submitted tasks. The function must have no return + * value, and can either take one argument, the thread index of type + * `std::size_t`, or zero arguments. It will be executed exactly once per + * thread, when the thread is first constructed. The initialization function + * must not throw any exceptions, as that will result in program + * termination. Any exceptions must be handled explicitly within the + * function. + */ + template + explicit thread_pool(F&& init) : thread_pool(0, std::forward(init)) {} + + /** + * @brief Construct a new thread pool with the specified number of threads + * and initialization function. + * + * @param num_threads The number of threads to use. + * @param init An initialization function to run in each thread before it + * starts executing any submitted tasks. The function must have no return + * value, and can either take one argument, the thread index of type + * `std::size_t`, or zero arguments. It will be executed exactly once per + * thread, when the thread is first constructed. The initialization function + * must not throw any exceptions, as that will result in program + * termination. Any exceptions must be handled explicitly within the + * function. + */ + template + thread_pool(const std::size_t num_threads, F&& init) { + create_threads(num_threads, std::forward(init)); + } + + // The copy and move constructors and assignment operators are deleted. The + // thread pool cannot be copied or moved. + thread_pool(const thread_pool&) = delete; + thread_pool(thread_pool&&) = delete; + thread_pool& operator=(const thread_pool&) = delete; + thread_pool& operator=(thread_pool&&) = delete; + + /** + * @brief Destruct the thread pool. Waits for all tasks to complete, then + * destroys all threads. If a cleanup function was set, it will run in each + * thread right before it is destroyed. Note that if the pool is paused, + * then any tasks still in the queue will never be executed. + */ + ~thread_pool() noexcept { +#ifdef __cpp_exceptions + try { +#endif + wait(); +#ifndef __cpp_lib_jthread + destroy_threads(); +#endif +#ifdef __cpp_exceptions + } catch (...) { + } +#endif + } + + // ======================= + // Public member functions + // ======================= + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and + * submitting each block separately to the queue, with the specified + * priority. The block function takes two arguments, the start and end of + * the block, so that it is only called once per block, but it is up to the + * user make sure the block function correctly deals with all the indices in + * each block. Does not return a `BS::multi_future`, so the user must use + * `wait()` or some other method to ensure that the loop finishes executing, + * otherwise bad things will happen. + * + * @tparam T1 The type of the first index. Should be a signed or unsigned + * integer. + * @tparam T2 The type of the index after the last index. Should be a signed + * or unsigned integer. + * @tparam F The type of the function to loop through. + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The + * loop will iterate from `first_index` to `(index_after_last - 1)` + * inclusive. In other words, it will be equivalent to `for (T i = + * first_index; i < index_after_last; ++i)`. Note that if `index_after_last + * <= first_index`, no blocks will be submitted. + * @param block A function that will be called once per block. Should take + * exactly two arguments: the first index in the block and the index after + * the last index in the block. `block(start, end)` should typically involve + * a loop of the form `for (T i = start; i < end; ++i)`. + * @param num_blocks The maximum number of blocks to split the loop into. + * The default is 0, which means the number of blocks will be equal to the + * number of threads in the pool. + * @param priority The priority of the tasks. Should be between -128 and + * +127 (a signed 8-bit integer). The default is 0. Only taken into account + * if the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + */ + template , typename F> + void detach_blocks(const T1 first_index, const T2 index_after_last, + F&& block, const std::size_t num_blocks = 0, + const priority_t priority = 0) { + if (static_cast(index_after_last) > static_cast(first_index)) { + const std::shared_ptr> block_ptr = + std::make_shared>(std::forward(block)); + const blocks blks(static_cast(first_index), + static_cast(index_after_last), + num_blocks ? num_blocks : thread_count); + for (std::size_t blk = 0; blk < blks.get_num_blocks(); ++blk) { + detach_task([block_ptr, start = blks.start(blk), + end = blks.end(blk)] { (*block_ptr)(start, end); }, + priority); + } + } + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and + * submitting each block separately to the queue, with the specified + * priority. The loop function takes one argument, the loop index, so that + * it is called many times per block. Does not return a `BS::multi_future`, + * so the user must use `wait()` or some other method to ensure that the + * loop finishes executing, otherwise bad things will happen. + * + * @tparam T1 The type of the first index. Should be a signed or unsigned + * integer. + * @tparam T2 The type of the index after the last index. Should be a signed + * or unsigned integer. + * @tparam F The type of the function to loop through. + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The + * loop will iterate from `first_index` to `(index_after_last - 1)` + * inclusive. In other words, it will be equivalent to `for (T i = + * first_index; i < index_after_last; ++i)`. Note that if `index_after_last + * <= first_index`, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per index, + * many times per block. Should take exactly one argument: the loop index. + * @param num_blocks The maximum number of blocks to split the loop into. + * The default is 0, which means the number of blocks will be equal to the + * number of threads in the pool. + * @param priority The priority of the tasks. Should be between -128 and + * +127 (a signed 8-bit integer). The default is 0. Only taken into account + * if the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + */ + template , typename F> + void detach_loop(const T1 first_index, const T2 index_after_last, F&& loop, + const std::size_t num_blocks = 0, + const priority_t priority = 0) { + if (static_cast(index_after_last) > static_cast(first_index)) { + const std::shared_ptr> loop_ptr = + std::make_shared>(std::forward(loop)); + const blocks blks(static_cast(first_index), + static_cast(index_after_last), + num_blocks ? num_blocks : thread_count); + for (std::size_t blk = 0; blk < blks.get_num_blocks(); ++blk) { + detach_task( + [loop_ptr, start = blks.start(blk), end = blks.end(blk)] { + for (T i = start; i < end; ++i) (*loop_ptr)(i); + }, + priority); + } + } + } + + /** + * @brief Submit a sequence of tasks enumerated by indices to the queue, + * with the specified priority. The sequence function takes one argument, + * the task index, and will be called once per index. Does not return a + * `BS::multi_future`, so the user must use `wait()` or some other method to + * ensure that the sequence finishes executing, otherwise bad things will + * happen. + * + * @tparam T1 The type of the first index. Should be a signed or unsigned + * integer. + * @tparam T2 The type of the index after the last index. Should be a signed + * or unsigned integer. + * @tparam F The type of the function used to define the sequence. + * @param first_index The first index in the sequence. + * @param index_after_last The index after the last index in the sequence. + * The sequence will iterate from `first_index` to `(index_after_last - 1)` + * inclusive. In other words, it will be equivalent to `for (T i = + * first_index; i < index_after_last; ++i)`. Note that if `index_after_last + * <= first_index`, no tasks will be submitted. + * @param sequence The function used to define the sequence. Will be called + * once per index. Should take exactly one argument, the index. + * @param priority The priority of the tasks. Should be between -128 and + * +127 (a signed 8-bit integer). The default is 0. Only taken into account + * if the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + */ + template , typename F> + void detach_sequence(const T1 first_index, const T2 index_after_last, + F&& sequence, const priority_t priority = 0) { + if (static_cast(index_after_last) > static_cast(first_index)) { + const std::shared_ptr> sequence_ptr = + std::make_shared>(std::forward(sequence)); + for (T i = static_cast(first_index); + i < static_cast(index_after_last); ++i) { + detach_task([sequence_ptr, i] { (*sequence_ptr)(i); }, priority); + } + } + } + + /** + * @brief Submit a function with no arguments and no return value into the + * task queue, with the specified priority. To submit a function with + * arguments, enclose it in a lambda expression. Does not return a future, + * so the user must use `wait()` or some other method to ensure that the + * task finishes executing, otherwise bad things will happen. + * + * @tparam F The type of the function. + * @param task The function to submit. + * @param priority The priority of the task. Should be between -128 and +127 + * (a signed 8-bit integer). The default is 0. Only taken into account if + * the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + */ + template + void detach_task(F&& task, const priority_t priority = 0) { + { + const std::scoped_lock tasks_lock(tasks_mutex); + if constexpr (priority_enabled) + tasks.emplace(std::forward(task), priority); + else + tasks.emplace(std::forward(task)); + } + task_available_cv.notify_one(); + } + +#ifdef BS_THREAD_POOL_NATIVE_EXTENSIONS + /** + * @brief Get a vector containing the underlying implementation-defined + * thread handles for each of the pool's threads, as obtained by + * `std::thread::native_handle()` (or `std::jthread::native_handle()` in + * C++20 and later). + * + * @return The native thread handles. + */ + [[nodiscard]] std::vector get_native_handles() + const { + std::vector native_handles(thread_count); + for (std::size_t i = 0; i < thread_count; ++i) + native_handles[i] = threads[i].native_handle(); + return native_handles; + } +#endif + + /** + * @brief Get the number of tasks currently waiting in the queue to be + * executed by the threads. + * + * @return The number of queued tasks. + */ + [[nodiscard]] std::size_t get_tasks_queued() const { + const std::scoped_lock tasks_lock(tasks_mutex); + return tasks.size(); + } + + /** + * @brief Get the number of tasks currently being executed by the threads. + * + * @return The number of running tasks. + */ + [[nodiscard]] std::size_t get_tasks_running() const { + const std::scoped_lock tasks_lock(tasks_mutex); + return tasks_running; + } + + /** + * @brief Get the total number of unfinished tasks: either still waiting in + * the queue, or running in a thread. Note that `get_tasks_total() == + * get_tasks_queued() + get_tasks_running()`. + * + * @return The total number of tasks. + */ + [[nodiscard]] std::size_t get_tasks_total() const { + const std::scoped_lock tasks_lock(tasks_mutex); + return tasks_running + tasks.size(); + } + + /** + * @brief Get the number of threads in the pool. + * + * @return The number of threads. + */ + [[nodiscard]] std::size_t get_thread_count() const noexcept { + return thread_count; + } + + /** + * @brief Get a vector containing the unique identifiers for each of the + * pool's threads, as obtained by `std::thread::get_id()` (or + * `std::jthread::get_id()` in C++20 and later). + * + * @return The unique thread identifiers. + */ + [[nodiscard]] std::vector get_thread_ids() const { + std::vector thread_ids(thread_count); + for (std::size_t i = 0; i < thread_count; ++i) + thread_ids[i] = threads[i].get_id(); + return thread_ids; + } + + /** + * @brief Check whether the pool is currently paused. Only enabled if the + * flag `BS:tp::pause` is enabled in the template parameter. + * + * @return `true` if the pool is paused, `false` if it is not paused. + */ + BS_THREAD_POOL_IF_PAUSE_ENABLED + [[nodiscard]] bool is_paused() const { + const std::scoped_lock tasks_lock(tasks_mutex); + return paused; + } + + /** + * @brief Pause the pool. The workers will temporarily stop retrieving new + * tasks out of the queue, although any tasks already executed will keep + * running until they are finished. Only enabled if the flag `BS:tp::pause` + * is enabled in the template parameter. + */ + BS_THREAD_POOL_IF_PAUSE_ENABLED + void pause() { + const std::scoped_lock tasks_lock(tasks_mutex); + paused = true; + } + + /** + * @brief Purge all the tasks waiting in the queue. Tasks that are currently + * running will not be affected, but any tasks still waiting in the queue + * will be discarded, and will never be executed by the threads. Please note + * that there is no way to restore the purged tasks. + */ + void purge() { + const std::scoped_lock tasks_lock(tasks_mutex); + tasks = {}; + } + + /** + * @brief Reset the pool with the total number of hardware threads + * available, as reported by the implementation. Waits for all currently + * running tasks to be completed, then destroys all threads in the pool and + * creates a new thread pool with the new number of threads. Any tasks that + * were waiting in the queue before the pool was reset will then be executed + * by the new threads. If the pool was paused before resetting it, the new + * pool will be paused as well. + */ + void reset() { + reset(0, [](std::size_t) {}); + } + + /** + * @brief Reset the pool with a new number of threads. Waits for all + * currently running tasks to be completed, then destroys all threads in the + * pool and creates a new thread pool with the new number of threads. Any + * tasks that were waiting in the queue before the pool was reset will then + * be executed by the new threads. If the pool was paused before resetting + * it, the new pool will be paused as well. + * + * @param num_threads The number of threads to use. + */ + void reset(const std::size_t num_threads) { + reset(num_threads, [](std::size_t) {}); + } + + /** + * @brief Reset the pool with the total number of hardware threads + * available, as reported by the implementation, and a new initialization + * function. Waits for all currently running tasks to be completed, then + * destroys all threads in the pool and creates a new thread pool with the + * new number of threads and initialization function. Any tasks that were + * waiting in the queue before the pool was reset will then be executed by + * the new threads. If the pool was paused before resetting it, the new pool + * will be paused as well. + * + * @param init An initialization function to run in each thread before it + * starts executing any submitted tasks. The function must have no return + * value, and can either take one argument, the thread index of type + * `std::size_t`, or zero arguments. It will be executed exactly once per + * thread, when the thread is first constructed. The initialization function + * must not throw any exceptions, as that will result in program + * termination. Any exceptions must be handled explicitly within the + * function. + */ + template + void reset(F&& init) { + reset(0, std::forward(init)); + } + + /** + * @brief Reset the pool with a new number of threads and a new + * initialization function. Waits for all currently running tasks to be + * completed, then destroys all threads in the pool and creates a new thread + * pool with the new number of threads and initialization function. Any + * tasks that were waiting in the queue before the pool was reset will then + * be executed by the new threads. If the pool was paused before resetting + * it, the new pool will be paused as well. + * + * @param num_threads The number of threads to use. + * @param init An initialization function to run in each thread before it + * starts executing any submitted tasks. The function must have no return + * value, and can either take one argument, the thread index of type + * `std::size_t`, or zero arguments. It will be executed exactly once per + * thread, when the thread is first constructed. The initialization function + * must not throw any exceptions, as that will result in program + * termination. Any exceptions must be handled explicitly within the + * function. + */ + template + void reset(const std::size_t num_threads, F&& init) { + if constexpr (pause_enabled) { + std::unique_lock tasks_lock(tasks_mutex); + const bool was_paused = paused; + paused = true; + tasks_lock.unlock(); + reset_pool(num_threads, std::forward(init)); + tasks_lock.lock(); + paused = was_paused; + } else { + reset_pool(num_threads, std::forward(init)); + } + } + + /** + * @brief Set the thread pool's cleanup function. + * + * @param cleanup A cleanup function to run in each thread right before it + * is destroyed, which will happen when the pool is destructed or reset. The + * function must have no return value, and can either take one argument, the + * thread index of type `std::size_t`, or zero arguments. The cleanup + * function must not throw any exceptions, as that will result in program + * termination. Any exceptions must be handled explicitly within the + * function. + */ + template + void set_cleanup_func(F&& cleanup) { + if constexpr (std::is_invocable_v) { + cleanup_func = std::forward(cleanup); + } else { + cleanup_func = [cleanup = std::forward(cleanup)](std::size_t) { + cleanup(); + }; + } + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and + * submitting each block separately to the queue, with the specified + * priority. The block function takes two arguments, the start and end of + * the block, so that it is only called once per block, but it is up to the + * user make sure the block function correctly deals with all the indices in + * each block. Returns a `BS::multi_future` that contains the futures for + * all of the blocks. + * + * @tparam T1 The type of the first index. Should be a signed or unsigned + * integer. + * @tparam T2 The type of the index after the last index. Should be a signed + * or unsigned integer. + * @tparam F The type of the function to loop through. + * @tparam R The return type of the function to loop through (can be + * `void`). + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The + * loop will iterate from `first_index` to `(index_after_last - 1)` + * inclusive. In other words, it will be equivalent to `for (T i = + * first_index; i < index_after_last; ++i)`. Note that if `index_after_last + * <= first_index`, no blocks will be submitted, and an empty + * `BS::multi_future` will be returned. + * @param block A function that will be called once per block. Should take + * exactly two arguments: the first index in the block and the index after + * the last index in the block. `block(start, end)` should typically involve + * a loop of the form `for (T i = start; i < end; ++i)`. + * @param num_blocks The maximum number of blocks to split the loop into. + * The default is 0, which means the number of blocks will be equal to the + * number of threads in the pool. + * @param priority The priority of the tasks. Should be between -128 and + * +127 (a signed 8-bit integer). The default is 0. Only taken into account + * if the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + * @return A `BS::multi_future` that can be used to wait for all the blocks + * to finish. If the block function returns a value, the `BS::multi_future` + * can also be used to obtain the values returned by each block. + */ + template , typename F, + typename R = std::invoke_result_t, T, T>> + [[nodiscard]] multi_future submit_blocks( + const T1 first_index, const T2 index_after_last, F&& block, + const std::size_t num_blocks = 0, const priority_t priority = 0) { + if (static_cast(index_after_last) > static_cast(first_index)) { + const std::shared_ptr> block_ptr = + std::make_shared>(std::forward(block)); + const blocks blks(static_cast(first_index), + static_cast(index_after_last), + num_blocks ? num_blocks : thread_count); + multi_future future; + future.reserve(blks.get_num_blocks()); + for (std::size_t blk = 0; blk < blks.get_num_blocks(); ++blk) { + future.push_back(submit_task( + [block_ptr, start = blks.start(blk), end = blks.end(blk)] { + return (*block_ptr)(start, end); + }, + priority)); + } + return future; + } + return {}; + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and + * submitting each block separately to the queue, with the specified + * priority. The loop function takes one argument, the loop index, so that + * it is called many times per block. It must have no return value. Returns + * a `BS::multi_future` that contains the futures for all of the blocks. + * + * @tparam T1 The type of the first index. Should be a signed or unsigned + * integer. + * @tparam T2 The type of the index after the last index. Should be a signed + * or unsigned integer. + * @tparam F The type of the function to loop through. + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The + * loop will iterate from `first_index` to `(index_after_last - 1)` + * inclusive. In other words, it will be equivalent to `for (T i = + * first_index; i < index_after_last; ++i)`. Note that if `index_after_last + * <= first_index`, no tasks will be submitted, and an empty + * `BS::multi_future` will be returned. + * @param loop The function to loop through. Will be called once per index, + * many times per block. Should take exactly one argument: the loop index. + * It cannot have a return value. + * @param num_blocks The maximum number of blocks to split the loop into. + * The default is 0, which means the number of blocks will be equal to the + * number of threads in the pool. + * @param priority The priority of the tasks. Should be between -128 and + * +127 (a signed 8-bit integer). The default is 0. Only taken into account + * if the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + * @return A `BS::multi_future` that can be used to wait for all the blocks + * to finish. + */ + template , typename F> + [[nodiscard]] multi_future submit_loop( + const T1 first_index, const T2 index_after_last, F&& loop, + const std::size_t num_blocks = 0, const priority_t priority = 0) { + if (static_cast(index_after_last) > static_cast(first_index)) { + const std::shared_ptr> loop_ptr = + std::make_shared>(std::forward(loop)); + const blocks blks(static_cast(first_index), + static_cast(index_after_last), + num_blocks ? num_blocks : thread_count); + multi_future future; + future.reserve(blks.get_num_blocks()); + for (std::size_t blk = 0; blk < blks.get_num_blocks(); ++blk) { + future.push_back(submit_task( + [loop_ptr, start = blks.start(blk), end = blks.end(blk)] { + for (T i = start; i < end; ++i) (*loop_ptr)(i); + }, + priority)); + } + return future; + } + return {}; + } + + /** + * @brief Submit a sequence of tasks enumerated by indices to the queue, + * with the specified priority. The sequence function takes one argument, + * the task index, and will be called once per index. Returns a + * `BS::multi_future` that contains the futures for all of the tasks. + * + * @tparam T1 The type of the first index. Should be a signed or unsigned + * integer. + * @tparam T2 The type of the index after the last index. Should be a signed + * or unsigned integer. + * @tparam F The type of the function used to define the sequence. + * @tparam R The return type of the function used to define the sequence + * (can be `void`). + * @param first_index The first index in the sequence. + * @param index_after_last The index after the last index in the sequence. + * The sequence will iterate from `first_index` to `(index_after_last - 1)` + * inclusive. In other words, it will be equivalent to `for (T i = + * first_index; i < index_after_last; ++i)`. Note that if `index_after_last + * <= first_index`, no tasks will be submitted, and an empty + * `BS::multi_future` will be returned. + * @param sequence The function used to define the sequence. Will be called + * once per index. Should take exactly one argument, the index. + * @param priority The priority of the tasks. Should be between -128 and + * +127 (a signed 8-bit integer). The default is 0. Only taken into account + * if the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + * @return A `BS::multi_future` that can be used to wait for all the tasks + * to finish. If the sequence function returns a value, the + * `BS::multi_future` can also be used to obtain the values returned by each + * task. + */ + template , typename F, + typename R = std::invoke_result_t, T>> + [[nodiscard]] multi_future submit_sequence( + const T1 first_index, const T2 index_after_last, F&& sequence, + const priority_t priority = 0) { + if (static_cast(index_after_last) > static_cast(first_index)) { + const std::shared_ptr> sequence_ptr = + std::make_shared>(std::forward(sequence)); + multi_future future; + future.reserve(static_cast( + static_cast(index_after_last) > static_cast(first_index))); + for (T i = static_cast(first_index); + i < static_cast(index_after_last); ++i) { + future.push_back(submit_task( + [sequence_ptr, i] { return (*sequence_ptr)(i); }, priority)); + } + return future; + } + return {}; + } + + /** + * @brief Submit a function with no arguments into the task queue, with the + * specified priority. To submit a function with arguments, enclose it in a + * lambda expression. If the function has a return value, get a future for + * the eventual returned value. If the function has no return value, get an + * `std::future` which can be used to wait until the task finishes. + * + * @tparam F The type of the function. + * @tparam R The return type of the function (can be `void`). + * @param task The function to submit. + * @param priority The priority of the task. Should be between -128 and +127 + * (a signed 8-bit integer). The default is 0. Only taken into account if + * the flag `BS:tp::priority` is enabled in the template parameter, + * otherwise has no effect. + * @return A future to be used later to wait for the function to finish + * executing and/or obtain its returned value if it has one. + */ + template >> + [[nodiscard]] std::future submit_task(F&& task, + const priority_t priority = 0) { +#ifdef __cpp_lib_move_only_function + std::promise promise; +#define BS_THREAD_POOL_PROMISE_MEMBER_ACCESS promise. +#else + const std::shared_ptr> promise = + std::make_shared>(); +#define BS_THREAD_POOL_PROMISE_MEMBER_ACCESS promise-> +#endif + std::future future = BS_THREAD_POOL_PROMISE_MEMBER_ACCESS get_future(); + detach_task( + [task = std::forward(task), + promise = std::move(promise)]() mutable { +#ifdef __cpp_exceptions + try { +#endif + if constexpr (std::is_void_v) { + task(); + BS_THREAD_POOL_PROMISE_MEMBER_ACCESS set_value(); + } else { + BS_THREAD_POOL_PROMISE_MEMBER_ACCESS set_value(task()); + } +#ifdef __cpp_exceptions + } catch (...) { + try { + BS_THREAD_POOL_PROMISE_MEMBER_ACCESS set_exception( + std::current_exception()); + } catch (...) { + } + } +#endif + }, + priority); + return future; + } + + /** + * @brief Unpause the pool. The workers will resume retrieving new tasks out + * of the queue. Only enabled if the flag `BS:tp::pause` is enabled in the + * template parameter. + */ + BS_THREAD_POOL_IF_PAUSE_ENABLED + void unpause() { + { + const std::scoped_lock tasks_lock(tasks_mutex); + paused = false; + } + task_available_cv.notify_all(); + } + + /** + * @brief Wait for tasks to be completed. Normally, this function waits for + * all tasks, both those that are currently running in the threads and those + * that are still waiting in the queue. However, if the pool is paused, this + * function only waits for the currently running tasks (otherwise it would + * wait forever). Note: To wait for just one specific task, use + * `submit_task()` instead, and call the `wait()` member function of the + * generated future. + * + * @throws `wait_deadlock` if called from within a thread of the same pool, + * which would result in a deadlock. Only enabled if the flag + * `BS:tp::wait_deadlock_checks` is enabled in the template parameter. + */ + void wait() { +#ifdef __cpp_exceptions + if constexpr (wait_deadlock_checks_enabled) { + if (this_thread::get_pool() == this) throw wait_deadlock(); + } +#endif + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + tasks_done_cv.wait(tasks_lock, [this] { + if constexpr (pause_enabled) + return (tasks_running == 0) && (paused || tasks.empty()); + else + return (tasks_running == 0) && tasks.empty(); + }); + waiting = false; + } + + /** + * @brief Wait for tasks to be completed, but stop waiting after the + * specified duration has passed. + * + * @tparam R An arithmetic type representing the number of ticks to wait. + * @tparam P An `std::ratio` representing the length of each tick in + * seconds. + * @param duration The amount of time to wait. + * @return `true` if all tasks finished running, `false` if the duration + * expired but some tasks are still running. + * @throws `wait_deadlock` if called from within a thread of the same pool, + * which would result in a deadlock. Only enabled if the flag + * `BS:tp::wait_deadlock_checks` is enabled in the template parameter. + */ + template + bool wait_for(const std::chrono::duration& duration) { +#ifdef __cpp_exceptions + if constexpr (wait_deadlock_checks_enabled) { + if (this_thread::get_pool() == this) throw wait_deadlock(); + } +#endif + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + const bool status = tasks_done_cv.wait_for(tasks_lock, duration, [this] { + if constexpr (pause_enabled) + return (tasks_running == 0) && (paused || tasks.empty()); + else + return (tasks_running == 0) && tasks.empty(); + }); + waiting = false; + return status; + } + + /** + * @brief Wait for tasks to be completed, but stop waiting after the + * specified time point has been reached. + * + * @tparam C The type of the clock used to measure time. + * @tparam D An `std::chrono::duration` type used to indicate the time + * point. + * @param timeout_time The time point at which to stop waiting. + * @return `true` if all tasks finished running, `false` if the time point + * was reached but some tasks are still running. + * @throws `wait_deadlock` if called from within a thread of the same pool, + * which would result in a deadlock. Only enabled if the flag + * `BS:tp::wait_deadlock_checks` is enabled in the template parameter. + */ + template + bool wait_until(const std::chrono::time_point& timeout_time) { +#ifdef __cpp_exceptions + if constexpr (wait_deadlock_checks_enabled) { + if (this_thread::get_pool() == this) throw wait_deadlock(); + } +#endif + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + const bool status = + tasks_done_cv.wait_until(tasks_lock, timeout_time, [this] { + if constexpr (pause_enabled) + return (tasks_running == 0) && (paused || tasks.empty()); + else + return (tasks_running == 0) && tasks.empty(); + }); + waiting = false; + return status; + } + + private: + // ======================== + // Private member functions + // ======================== + + /** + * @brief Create the threads in the pool and assign a worker to each thread. + * + * @param num_threads The number of threads to use. + * @param init An initialization function to run in each thread before it + * starts executing any submitted tasks. + */ + template + void create_threads(const std::size_t num_threads, F&& init) { + if constexpr (std::is_invocable_v) { + init_func = std::forward(init); + } else { + init_func = [init = std::forward(init)](std::size_t) { init(); }; + } + thread_count = determine_thread_count(num_threads); + threads = std::make_unique(thread_count); + { + const std::scoped_lock tasks_lock(tasks_mutex); + tasks_running = thread_count; +#ifndef __cpp_lib_jthread + workers_running = true; +#endif + } + for (std::size_t i = 0; i < thread_count; ++i) { + threads[i] = thread_t( + [this, i] +#ifdef __cpp_lib_jthread + (const std::stop_token& stop_token) { worker(stop_token, i); } +#else + { worker(i); } +#endif + ); + } + } + +#ifndef __cpp_lib_jthread + /** + * @brief Destroy the threads in the pool. + */ + void destroy_threads() { + { + const std::scoped_lock tasks_lock(tasks_mutex); + workers_running = false; + } + task_available_cv.notify_all(); + for (std::size_t i = 0; i < thread_count; ++i) threads[i].join(); + } +#endif + + /** + * @brief Determine how many threads the pool should have, based on the + * parameter passed to the constructor or reset(). + * + * @param num_threads The parameter passed to the constructor or `reset()`. + * If the parameter is a positive number, then the pool will be created with + * this number of threads. If the parameter is non-positive, or a parameter + * was not supplied (in which case it will have the default value of 0), + * then the pool will be created with the total number of hardware threads + * available, as obtained from `thread_t::hardware_concurrency()`. If the + * latter returns zero for some reason, then the pool will be created with + * just one thread. + * @return The number of threads to use for constructing the pool. + */ + [[nodiscard]] static std::size_t determine_thread_count( + const std::size_t num_threads) noexcept { + if (num_threads > 0) return num_threads; + if (thread_t::hardware_concurrency() > 0) + return thread_t::hardware_concurrency(); + return 1; + } + + /** + * @brief Pop a task from the queue. + * + * @return The task. + */ + [[nodiscard]] task_t pop_task() { + task_t task; + if constexpr (priority_enabled) + task = std::move(const_cast(tasks.top()).task); + else + task = std::move(tasks.front()); + tasks.pop(); + return task; + } + + /** + * @brief Reset the pool with a new number of threads and a new + * initialization function. This member function implements the actual + * reset, while the public member function `reset()` also handles the case + * where the pool is paused. + * + * @param num_threads The number of threads to use. + * @param init An initialization function to run in each thread before it + * starts executing any submitted tasks. + */ + template + void reset_pool(const std::size_t num_threads, F&& init) { + wait(); +#ifndef __cpp_lib_jthread + destroy_threads(); +#endif + create_threads(num_threads, std::forward(init)); + } + + /** + * @brief A worker function to be assigned to each thread in the pool. Waits + * until it is notified by `detach_task()` that a task is available, and + * then retrieves the task from the queue and executes it. Once the task + * finishes, the worker notifies `wait()` in case it is waiting. + * + * @param idx The index of this thread. + */ + void worker(BS_THREAD_POOL_WORKER_TOKEN const std::size_t idx) { + this_thread::my_pool = this; + this_thread::my_index = idx; + init_func(idx); + while (true) { + std::unique_lock tasks_lock(tasks_mutex); + --tasks_running; + if constexpr (pause_enabled) { + if (waiting && (tasks_running == 0) && (paused || tasks.empty())) + tasks_done_cv.notify_all(); + } else { + if (waiting && (tasks_running == 0) && tasks.empty()) + tasks_done_cv.notify_all(); + } + task_available_cv.wait(tasks_lock BS_THREAD_POOL_WAIT_TOKEN, [this] { + if constexpr (pause_enabled) + return !(paused || tasks.empty()) BS_THREAD_POOL_OR_STOP_CONDITION; + else + return !tasks.empty() BS_THREAD_POOL_OR_STOP_CONDITION; + }); + if (BS_THREAD_POOL_STOP_CONDITION) break; + { + task_t task = pop_task(); // NOLINT(misc-const-correctness) In C++23 + // this cannot be const since + // `std::move_only_function::operator()` is + // not a const member function. + ++tasks_running; + tasks_lock.unlock(); +#ifdef __cpp_exceptions + try { +#endif + task(); +#ifdef __cpp_exceptions + } catch (...) { + } +#endif + } + } + cleanup_func(idx); + this_thread::my_index = std::nullopt; + this_thread::my_pool = std::nullopt; + } + + // ============ + // Private data + // ============ + + /** + * @brief A cleanup function to run in each thread right before it is + * destroyed, which will happen when the pool is destructed or reset. The + * function must have no return value, and can either take one argument, the + * thread index of type `std::size_t`, or zero arguments. The cleanup + * function must not throw any exceptions, as that will result in program + * termination. Any exceptions must be handled explicitly within the + * function. The default is an empty function, i.e., no cleanup will be + * performed. + */ + function_t cleanup_func = [](std::size_t) {}; + + /** + * @brief An initialization function to run in each thread before it starts + * executing any submitted tasks. The function must have no return value, + * and can either take one argument, the thread index of type `std::size_t`, + * or zero arguments. It will be executed exactly once per thread, when the + * thread is first constructed. The initialization function must not throw + * any exceptions, as that will result in program termination. Any + * exceptions must be handled explicitly within the function. The default is + * an empty function, i.e., no initialization will be performed. + */ + function_t init_func = [](std::size_t) {}; + + /** + * @brief A flag indicating whether the workers should pause. When set to + * `true`, the workers temporarily stop retrieving new tasks out of the + * queue, although any tasks already executed will keep running until they + * are finished. When set to `false` again, the workers resume retrieving + * tasks. Only enabled if the flag `BS:tp::pause` is enabled in the template + * parameter. + */ + std::conditional_t paused = {}; + +/** + * @brief A condition variable to notify `worker()` that a new task has become + * available. + */ +#ifdef __cpp_lib_jthread + std::condition_variable_any +#else + std::condition_variable +#endif + task_available_cv; + + /** + * @brief A condition variable to notify `wait()` that the tasks are done. + */ + std::condition_variable tasks_done_cv; + + /** + * @brief A queue of tasks to be executed by the threads. + */ + std::conditional_t, + std::queue> + tasks; + + /** + * @brief A mutex to synchronize access to the task queue by different + * threads. + */ + mutable std::mutex tasks_mutex; + + /** + * @brief A counter for the total number of currently running tasks. + */ + std::size_t tasks_running = 0; + + /** + * @brief The number of threads in the pool. + */ + std::size_t thread_count = 0; + + /** + * @brief A smart pointer to manage the memory allocated for the threads. + */ + std::unique_ptr threads = nullptr; + + /** + * @brief A flag indicating that `wait()` is active and expects to be + * notified whenever a task is done. + */ + bool waiting = false; + +#ifndef __cpp_lib_jthread + /** + * @brief A flag indicating to the workers to keep running. When set to + * `false`, the workers terminate permanently. + */ + bool workers_running = false; +#endif + }; // class thread_pool + + /** + * @brief A utility class to synchronize printing to an output stream by + * different threads. + */ + class [[nodiscard]] synced_stream { + public: + /** + * @brief Construct a new synced stream which prints to `std::cout`. + */ + explicit synced_stream() { add_stream(std::cout); } + + /** + * @brief Construct a new synced stream which prints to the given output + * stream(s). + * + * @tparam T The types of the output streams to print to. + * @param streams The output streams to print to. + */ + template + explicit synced_stream(T&... streams) { + (add_stream(streams), ...); + } + + /** + * @brief Add a stream to the list of output streams to print to. + * + * @param stream The stream. + */ + void add_stream(std::ostream& stream) { out_streams.push_back(&stream); } + + /** + * @brief Get a reference to a vector containing pointers to the output + * streams to print to. + * + * @return The output streams. + */ + std::vector& get_streams() noexcept { return out_streams; } + + /** + * @brief Print any number of items into the output stream. Ensures that no + * other threads print to this stream simultaneously, as long as they all + * exclusively use the same `BS::synced_stream` object to print. + * + * @tparam T The types of the items. + * @param items The items to print. + */ + template + void print(const T&... items) { + const std::scoped_lock stream_lock(stream_mutex); + for (std::ostream* const stream : out_streams) (*stream << ... << items); + } + + /** + * @brief Print any number of items into the output stream, followed by a + * newline character. Ensures that no other threads print to this stream + * simultaneously, as long as they all exclusively use the same + * `BS::synced_stream` object to print. + * + * @tparam T The types of the items. + * @param items The items to print. + */ + template + void println(T&&... items) { + print(std::forward(items)..., '\n'); + } + + /** + * @brief Remove a stream from the list of output streams to print to. + * + * @param stream The stream. + */ + void remove_stream(std::ostream& stream) { + out_streams.erase( + std::remove(out_streams.begin(), out_streams.end(), &stream), + out_streams.end()); + } + + /** + * @brief A stream manipulator to pass to a `BS::synced_stream` (an explicit + * cast of `std::endl`). Prints a newline character to the stream, and then + * flushes it. Should only be used if flushing is desired, otherwise a + * newline character should be used instead. + */ + inline static std::ostream& (&endl)(std::ostream&) = + static_cast(std::endl); + + /** + * @brief A stream manipulator to pass to a `BS::synced_stream` (an explicit + * cast of `std::flush`). Used to flush the stream. + */ + inline static std::ostream& (&flush)(std::ostream&) = + static_cast(std::flush); + + private: + /** + * @brief The output streams to print to. + */ + std::vector out_streams; + + /** + * @brief A mutex to synchronize printing. + */ + mutable std::mutex stream_mutex; + }; // class synced_stream + +#ifdef __cpp_lib_semaphore + using binary_semaphore = std::binary_semaphore; + template ::max()> + using counting_semaphore = std::counting_semaphore; +#else + /** + * @brief A polyfill for `std::counting_semaphore`, to be used if C++20 + * features are not available. A `counting_semaphore` is a synchronization + * primitive that allows more than one concurrent access to the same resource. + * The number of concurrent accessors is limited by the semaphore's counter, + * which is decremented when a thread acquires the semaphore and incremented + * when a thread releases the semaphore. If the counter is zero, a thread + * trying to acquire the semaphore will be blocked until another thread + * releases the semaphore. + * + * @tparam LeastMaxValue The least maximum value of the counter. (In this + * implementation, it is also the actual maximum value.) + */ + template ::max()> + class [[nodiscard]] counting_semaphore { + static_assert(LeastMaxValue >= 0, + "The least maximum value for a counting semaphore must not " + "be negative."); + + public: + /** + * @brief Construct a new counting semaphore with the given initial counter + * value. + * + * @param desired The initial counter value. + */ + constexpr explicit counting_semaphore(const std::ptrdiff_t desired) + : counter(desired) {} + + // The copy and move constructors and assignment operators are deleted. The + // semaphore cannot be copied or moved. + counting_semaphore(const counting_semaphore&) = delete; + counting_semaphore(counting_semaphore&&) = delete; + counting_semaphore& operator=(const counting_semaphore&) = delete; + counting_semaphore& operator=(counting_semaphore&&) = delete; + ~counting_semaphore() = default; + + /** + * @brief Returns the internal counter's maximum possible value, which in + * this implementation is equal to `LeastMaxValue`. + * + * @return The internal counter's maximum possible value. + */ + [[nodiscard]] static constexpr std::ptrdiff_t max() noexcept { + return LeastMaxValue; + } + + /** + * @brief Atomically decrements the internal counter by 1 if it is greater + * than 0; otherwise blocks until it is greater than 0 and can successfully + * decrement the internal counter. + */ + void acquire() { + std::unique_lock lock(mutex); + cv.wait(lock, [this] { return counter > 0; }); + --counter; + } + + /** + * @brief Atomically increments the internal counter. Any thread(s) waiting + * for the counter to be greater than 0, such as due to being blocked in + * `acquire()`, will subsequently be unblocked. + * + * @param update The amount to increment the internal counter by. Defaults + * to 1. + */ + void release(const std::ptrdiff_t update = 1) { + { + const std::scoped_lock lock(mutex); + counter += update; + } + cv.notify_all(); + } + + /** + * @brief Tries to atomically decrement the internal counter by 1 if it is + * greater than 0; no blocking occurs regardless. + * + * @return `true` if decremented the internal counter, `false` otherwise. + */ + bool try_acquire() { + std::scoped_lock lock(mutex); + if (counter > 0) { + --counter; + return true; + } + return false; + } + + /** + * @brief Tries to atomically decrement the internal counter by 1 if it is + * greater than 0; otherwise blocks until it is greater than 0 and can + * successfully decrement the internal counter, or the `rel_time` duration + * has been exceeded. + * + * @tparam Rep An arithmetic type representing the number of ticks to wait. + * @tparam Period An `std::ratio` representing the length of each tick in + * seconds. + * @param rel_time The duration the function must wait. Note that the + * function may wait for longer. + * @return `true` if decremented the internal counter, `false` otherwise. + */ + template + bool try_acquire_for(const std::chrono::duration& rel_time) { + std::unique_lock lock(mutex); + if (!cv.wait_for(lock, rel_time, [this] { return counter > 0; })) + return false; + --counter; + return true; + } + + /** + * @brief Tries to atomically decrement the internal counter by 1 if it is + * greater than 0; otherwise blocks until it is greater than 0 and can + * successfully decrement the internal counter, or the `abs_time` time point + * has been passed. + * + * @tparam Clock The type of the clock used to measure time. + * @tparam Duration An `std::chrono::duration` type used to indicate the + * time point. + * @param abs_time The earliest time the function must wait until. Note that + * the function may wait for longer. + * @return `true` if decremented the internal counter, `false` otherwise. + */ + template + bool try_acquire_until( + const std::chrono::time_point& abs_time) { + std::unique_lock lock(mutex); + if (!cv.wait_until(lock, abs_time, [this] { return counter > 0; })) + return false; + --counter; + return true; + } + + private: + /** + * @brief The semaphore's counter. + */ + std::ptrdiff_t counter; + + /** + * @brief A condition variable used to wait for the counter. + */ + std::condition_variable cv; + + /** + * @brief A mutex used to synchronize access to the counter. + */ + mutable std::mutex mutex; + }; + + /** + * @brief A polyfill for `std::binary_semaphore`, to be used if C++20 features + * are not available. + */ + using binary_semaphore = counting_semaphore<1>; +#endif +} // namespace BS +#endif // BS_THREAD_POOL_HPP diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index 59d24c7a..ba9fb857 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -5,7 +5,7 @@ if(RF_BUILD_APPS) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") find_package(mimalloc CONFIG REQUIRED) - list(APPEND ROOFER_LINK_LIBRARIES $,mimalloc-static,mimalloc>) + list(APPEND ROOFER_LINK_LIBRARIES mimalloc) endif() add_executable("roofer" ${APP_SOURCES}) diff --git a/flake.lock b/flake.lock index d7eb991d..9b903ee0 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1746904237, - "narHash": "sha256-3e+AVBczosP5dCLQmMoMEogM57gmZ2qrVSrmq9aResQ=", - "owner": "NixOS", + "lastModified": 1754487508, + "narHash": "sha256-N3q2RRuJZk66yHEoT8SyxLSgR2t5/PYQPMU4nloapk4=", + "owner": "Ylannl", "repo": "nixpkgs", - "rev": "d89fc19e405cb2d55ce7cc114356846a0ee5e956", + "rev": "2ffbaa27f2ab094eda2f1e375c3349a3ef569095", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixos-unstable", + "owner": "Ylannl", + "ref": "cgal", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index de600033..37d465c0 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Development environment for Roofer"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + inputs.nixpkgs.url = "github:Ylannl/nixpkgs/cgal"; outputs = { nixpkgs, ... }: let @@ -10,7 +10,7 @@ in { devShells = forAllSystems (system: let - pkgs = nixpkgs.legacyPackages.${system}; + pkgs = import nixpkgs { system = system; config.allowUnfree = true; }; apple_sdk = pkgs.apple-sdk_15; py = pkgs.python313; in { @@ -20,27 +20,21 @@ } { buildInputs = with pkgs; [ cmakeCurses - vcpkg ninja - # to make vcpkg work - autoconf - automake - autoconf-archive - pkg-config-unwrapped - bash - cacert - coreutils - curl - gnumake - gzip - openssh - perl - pkg-config - libtool - zip - zstd - bison # thrift/arrow/rerun + # roofer deps + cgal + gmp + mpfr + boost + eigen + fmt + + # apps + mimalloc + gdal + nlohmann_json + LAStools # python tools py From fdd63110311be1e00dbffb6ca1a094065c4a7efb Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 6 Aug 2025 21:22:52 +0200 Subject: [PATCH 03/53] start work on nix build support --- CMakeLists.txt | 4 +- apps/CMakeLists.txt | 1 + .../cmake-git-version-tracking/CMakeLists.txt | 23 ++ apps/cmake-git-version-tracking/LICENSE | 21 + apps/cmake-git-version-tracking/README.md | 65 ++++ apps/cmake-git-version-tracking/git.c.in | 32 ++ apps/cmake-git-version-tracking/git.h | 149 +++++++ .../git_watcher.cmake | 367 ++++++++++++++++++ flake.nix | 62 +++ 9 files changed, 721 insertions(+), 3 deletions(-) create mode 100644 apps/cmake-git-version-tracking/CMakeLists.txt create mode 100644 apps/cmake-git-version-tracking/LICENSE create mode 100644 apps/cmake-git-version-tracking/README.md create mode 100644 apps/cmake-git-version-tracking/git.c.in create mode 100644 apps/cmake-git-version-tracking/git.h create mode 100644 apps/cmake-git-version-tracking/git_watcher.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 2977cf82..03f0cbf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,9 +82,7 @@ endif() # We have to use CPM (or FetchContent) even with vcpkg, because of # cmake-git-version-tracking, val3dity, rerun include(CPM) -cpmaddpackage( - "gh:andrew-hardin/cmake-git-version-tracking#6c0cb87edd029ddfb403a8e24577c144a03605a6" -) + set(GIT_IGNORE_UNTRACKED TRUE) if(RF_USE_RERUN) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index b24cd912..a14dc34e 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,5 +1,6 @@ include_directories("${CMAKE_CURRENT_SOURCE_DIR}/external") +add_subdirectory(cmake-git-version-tracking) add_subdirectory(roofer-app) # add polyscope diff --git a/apps/cmake-git-version-tracking/CMakeLists.txt b/apps/cmake-git-version-tracking/CMakeLists.txt new file mode 100644 index 00000000..5a5cd69f --- /dev/null +++ b/apps/cmake-git-version-tracking/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.2...3.27) +project(cmake_git_version_tracking + LANGUAGES C) + +# Define the two required variables before including +# the source code for watching a git repository. +set(PRE_CONFIGURE_FILE "git.c.in") +set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git.c") +include(git_watcher.cmake) + +# Create a library out of the compiled post-configure file. +# +# Note that the include is a system include. This was done +# so downstream projects don't suffer from warnings on a +# 3rdparty library. +add_library(${PROJECT_NAME} STATIC ${POST_CONFIGURE_FILE}) +target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(${PROJECT_NAME} check_git) + +# The C99 standard is only required because we're using . +# This could be removed if it's a problem for users, but would require the +# cmake configure() commands to translate true/false literals to 1/0. +set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) diff --git a/apps/cmake-git-version-tracking/LICENSE b/apps/cmake-git-version-tracking/LICENSE new file mode 100644 index 00000000..b0af5790 --- /dev/null +++ b/apps/cmake-git-version-tracking/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Andrew Hardin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/cmake-git-version-tracking/README.md b/apps/cmake-git-version-tracking/README.md new file mode 100644 index 00000000..d7506f9c --- /dev/null +++ b/apps/cmake-git-version-tracking/README.md @@ -0,0 +1,65 @@ +[![Regression Tests](https://github.com/andrew-hardin/cmake-git-version-tracking/actions/workflows/main.yml/badge.svg)](https://github.com/andrew-hardin/cmake-git-version-tracking/actions/workflows/main.yml) +# Embed Git metadata in C/C++ projects via CMake +This project embeds up-to-date git metadata in a standalone C/C++ static library via CMake. +It's written responsibly to only trigger rebuilds if git metadata changes (e.g. a new commit is added). +The core capability is baked into single self-contained +[script](git_watcher.cmake). + +## Requirements +- CMake >= 3.2 +- C Compiler (with C99 standard support) +- Git + +## Quickstart via FetchContent +You can use CMake's `FetchContent` module to build the static library `cmake_git_version_tracking`: +```cmake +include(FetchContent) +FetchContent_Declare(cmake_git_version_tracking + GIT_REPOSITORY https://github.com/andrew-hardin/cmake-git-version-tracking.git + GIT_TAG 904dbda1336ba4b9a1415a68d5f203f576b696bb +) +FetchContent_MakeAvailable(cmake_git_version_tracking) + +target_link_libraries(your_target + cmake_git_version_tracking +) +``` +Then [`#include git.h`](./git.h) and use the provided functions to retrieve git metadata. + +## Intended use case +You're continuously shipping prebuilt binaries for an +application. A user discovers a bug and files a bug report. +By embedding up-to-date versioning information, the user +can include this information in their report, e.g.: + +``` +Commit SHA1: 46a396e (46a396e6c1eb3d) +Dirty: false (there were no uncommitted changes at time of build) +``` + +This allows you to investigate the _precise_ version of the +application that the bug was reported in. + +## Q: What if I want to track `$special_git_field`? +Fork the project and modify [git_watcher.cmake](git_watcher.cmake) +to track new additional fields (e.g. kernel version or build hostname). +Sections that need to be modified are marked with `>>>`. + +## Q: Doesn't this already exist? +It depends on your specific requirements. Before writing this, I +found two categories of existing solutions: + +- Write the commit ID to the header at configure time (e.g. `cmake `). + This works well for automated build processes (e.g. check-in code and build artifacts). + However, any changes made after running `cmake` + (e.g. `git commit -am "Changed X"`) aren't reflected in the header. + +- Every time a build is started (e.g. `make`), write the commit ID to a header. + The major drawback of this method is that any object file that includes the new + header will be recompiled -- _even if the state of the git repo hasn't changed_. + +## Q: What's the better solution? +We check Git every time a build is started (e.g. `make`) to see if anything has changed, +like a new commit to the current branch. If nothing has changed, then we don't +touch anything- _no recompiling or linking is triggered_. If something has changed, then we +reconfigure the header and CMake rebuilds any downstream dependencies. diff --git a/apps/cmake-git-version-tracking/git.c.in b/apps/cmake-git-version-tracking/git.c.in new file mode 100644 index 00000000..a26d27c5 --- /dev/null +++ b/apps/cmake-git-version-tracking/git.c.in @@ -0,0 +1,32 @@ +#include "git.h" + +bool git_IsPopulated() { + return @GIT_RETRIEVED_STATE@; +} +bool git_AnyUncommittedChanges() { + return @GIT_IS_DIRTY@; +} +const char* git_AuthorName() { + return "@GIT_AUTHOR_NAME@"; +} +const char* git_AuthorEmail() { + return "@GIT_AUTHOR_EMAIL@"; +} +const char* git_CommitSHA1() { + return "@GIT_HEAD_SHA1@"; +} +const char* git_CommitDate() { + return "@GIT_COMMIT_DATE_ISO8601@"; +} +const char* git_CommitSubject() { + return "@GIT_COMMIT_SUBJECT@"; +} +const char* git_CommitBody() { + return "@GIT_COMMIT_BODY@"; +} +const char* git_Describe() { + return "@GIT_DESCRIBE@"; +} +const char* git_Branch() { + return "@GIT_BRANCH@"; +} diff --git a/apps/cmake-git-version-tracking/git.h b/apps/cmake-git-version-tracking/git.h new file mode 100644 index 00000000..ba95b2c9 --- /dev/null +++ b/apps/cmake-git-version-tracking/git.h @@ -0,0 +1,149 @@ +#pragma once +// git.h +// https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git.h +// +// Released under the MIT License. +// https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE + +#include + +#ifdef __cplusplus +#define GIT_VERSION_TRACKING_EXTERN_C_BEGIN extern "C" { +#define GIT_VERSION_TRACKING_EXTERN_C_END } +#else +#define GIT_VERSION_TRACKING_EXTERN_C_BEGIN +#define GIT_VERSION_TRACKING_EXTERN_C_END +#endif + +// Don't mangle the C function names if included in a CXX file. +GIT_VERSION_TRACKING_EXTERN_C_BEGIN + +/// Is the metadata populated? +// +/// We may not have metadata if there wasn't a .git directory +/// (e.g. downloaded source code without revision history). +bool git_IsPopulated(); + +/// Were there any uncommitted changes that won't be reflected +/// in the CommitID? +bool git_AnyUncommittedChanges(); + +/// The commit author's name. +const char* git_AuthorName(); + +/// The commit author's email. +const char* git_AuthorEmail(); + +/// The commit SHA1. +const char* git_CommitSHA1(); + +/// The ISO8601 commit date. +const char* git_CommitDate(); + +/// The commit subject. +const char* git_CommitSubject(); + +/// The commit body. +const char* git_CommitBody(); + +/// The commit describe. +const char* git_Describe(); + +/// The symbolic reference tied to HEAD. +const char* git_Branch(); + +GIT_VERSION_TRACKING_EXTERN_C_END +#undef GIT_VERSION_TRACKING_EXTERN_C_BEGIN +#undef GIT_VERSION_TRACKING_EXTERN_C_END + +#ifdef __cplusplus + +/// This is a utility extension for C++ projects. +/// It provides a "git" namespace that wraps the +/// C methods in more(?) ergonomic types. +/// +/// This is header-only in an effort to keep the +/// underlying static library C99 compliant. + +// We really want to use std::string_view if it appears +// that the compiler will support it. If that fails, +// revert back to std::string. +#define GIT_VERSION_TRACKING_CPP_17_STANDARD 201703L +#if __cplusplus >= GIT_VERSION_TRACKING_CPP_17_STANDARD +#define GIT_VERSION_USE_STRING_VIEW 1 +#else +#define GIT_VERSION_USE_STRING_VIEW 0 +#endif + +#if GIT_VERSION_USE_STRING_VIEW +#include +#include +#else +#include +#endif + +namespace git { + +#if GIT_VERSION_USE_STRING_VIEW + using StringOrView = std::string_view; +#else + typedef std::string StringOrView; +#endif + + namespace internal { + + /// Short-hand method for initializing a std::string or std::string_view + /// given a C-style const char*. + inline const StringOrView InitString(const char* from_c_interface) { +#if GIT_VERSION_USE_STRING_VIEW + return StringOrView{from_c_interface, std::strlen(from_c_interface)}; +#else + return std::string(from_c_interface); +#endif + } + + } // namespace internal + + inline bool IsPopulated() { return git_IsPopulated(); } + inline bool AnyUncommittedChanges() { return git_AnyUncommittedChanges(); } + inline const StringOrView& AuthorName() { + static const StringOrView kValue = internal::InitString(git_AuthorName()); + return kValue; + } + inline const StringOrView AuthorEmail() { + static const StringOrView kValue = internal::InitString(git_AuthorEmail()); + return kValue; + } + inline const StringOrView CommitSHA1() { + static const StringOrView kValue = internal::InitString(git_CommitSHA1()); + return kValue; + } + inline const StringOrView CommitDate() { + static const StringOrView kValue = internal::InitString(git_CommitDate()); + return kValue; + } + inline const StringOrView CommitSubject() { + static const StringOrView kValue = + internal::InitString(git_CommitSubject()); + return kValue; + } + inline const StringOrView CommitBody() { + static const StringOrView kValue = internal::InitString(git_CommitBody()); + return kValue; + } + inline const StringOrView Describe() { + static const StringOrView kValue = internal::InitString(git_Describe()); + return kValue; + } + inline const StringOrView Branch() { + static const StringOrView kValue = internal::InitString(git_Branch()); + return kValue; + } + +} // namespace git + +// Cleanup our defines to avoid polluting. +#undef GIT_VERSION_USE_STRING_VIEW +#undef GIT_VERSION_TRACKING_CPP_17_STANDARD + +#endif // __cplusplus diff --git a/apps/cmake-git-version-tracking/git_watcher.cmake b/apps/cmake-git-version-tracking/git_watcher.cmake new file mode 100644 index 00000000..f69295a3 --- /dev/null +++ b/apps/cmake-git-version-tracking/git_watcher.cmake @@ -0,0 +1,367 @@ +# git_watcher.cmake +# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake +# +# Released under the MIT License. +# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE + + +# This file defines a target that monitors the state of a git repo. +# If the state changes (e.g. a commit is made), then a file gets reconfigured. +# Here are the primary variables that control script behavior: +# +# PRE_CONFIGURE_FILE (REQUIRED) +# -- The path to the file that'll be configured. +# +# POST_CONFIGURE_FILE (REQUIRED) +# -- The path to the configured PRE_CONFIGURE_FILE. +# +# GIT_STATE_FILE (OPTIONAL) +# -- The path to the file used to store the previous build's git state. +# Defaults to the current binary directory. +# +# GIT_WORKING_DIR (OPTIONAL) +# -- The directory from which git commands will be run. +# Defaults to the directory with the top level CMakeLists.txt. +# +# GIT_EXECUTABLE (OPTIONAL) +# -- The path to the git executable. It'll automatically be set if the +# user doesn't supply a path. +# +# GIT_FAIL_IF_NONZERO_EXIT (OPTIONAL) +# -- Raise a FATAL_ERROR if any of the git commands return a non-zero +# exit code. This is set to TRUE by default. You can set this to FALSE +# if you'd like the build to continue even if a git command fails. +# +# GIT_IGNORE_UNTRACKED (OPTIONAL) +# -- Ignore the presence of untracked files when detecting if the +# working tree is dirty. This is set to FALSE by default. +# +# DESIGN +# - This script was designed similar to a Python application +# with a Main() function. I wanted to keep it compact to +# simplify "copy + paste" usage. +# +# - This script is invoked under two CMake contexts: +# 1. Configure time (when build files are created). +# 2. Build time (called via CMake -P). +# The first invocation is what registers the script to +# be executed at build time. +# +# MODIFICATIONS +# You may wish to track other git properties like when the last +# commit was made. There are two sections you need to modify, +# and they're tagged with a ">>>" header. + +# Short hand for converting paths to absolute. +macro(PATH_TO_ABSOLUTE var_name) + get_filename_component(${var_name} "${${var_name}}" ABSOLUTE) +endmacro() + +# Check that a required variable is set. +macro(CHECK_REQUIRED_VARIABLE var_name) + if(NOT DEFINED ${var_name}) + message(FATAL_ERROR "The \"${var_name}\" variable must be defined.") + endif() + PATH_TO_ABSOLUTE(${var_name}) +endmacro() + +# Check that an optional variable is set, or, set it to a default value. +macro(CHECK_OPTIONAL_VARIABLE_NOPATH var_name default_value) + if(NOT DEFINED ${var_name}) + set(${var_name} ${default_value}) + endif() +endmacro() + +# Check that an optional variable is set, or, set it to a default value. +# Also converts that path to an abspath. +macro(CHECK_OPTIONAL_VARIABLE var_name default_value) + CHECK_OPTIONAL_VARIABLE_NOPATH(${var_name} ${default_value}) + PATH_TO_ABSOLUTE(${var_name}) +endmacro() + +CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE) +CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE) +CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git-state-hash") +CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}") +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_FAIL_IF_NONZERO_EXIT TRUE) +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_IGNORE_UNTRACKED FALSE) + +# Check the optional git variable. +# If it's not set, we'll try to find it using the CMake packaging system. +if(NOT DEFINED GIT_EXECUTABLE) + find_package(Git QUIET REQUIRED) +endif() +CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE) + + +set(_state_variable_names + GIT_RETRIEVED_STATE + GIT_HEAD_SHA1 + GIT_IS_DIRTY + GIT_AUTHOR_NAME + GIT_AUTHOR_EMAIL + GIT_COMMIT_DATE_ISO8601 + GIT_COMMIT_SUBJECT + GIT_COMMIT_BODY + GIT_DESCRIBE + GIT_BRANCH + # >>> + # 1. Add the name of the additional git variable you're interested in monitoring + # to this list. +) + + + +# Macro: RunGitCommand +# Description: short-hand macro for calling a git function. Outputs are the +# "exit_code" and "output" variables. The "_permit_git_failure" +# variable can locally override the exit code checking- use it +# with caution. +macro(RunGitCommand) + execute_process(COMMAND + "${GIT_EXECUTABLE}" ${ARGV} + WORKING_DIRECTORY "${_working_dir}" + RESULT_VARIABLE exit_code + OUTPUT_VARIABLE output + ERROR_VARIABLE stderr + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT exit_code EQUAL 0 AND NOT _permit_git_failure) + set(ENV{GIT_RETRIEVED_STATE} "false") + + # Issue 26: git info not properly set + # + # Check if we should fail if any of the exit codes are non-zero. + # Most methods have a fall-back default value that's used in case of non-zero + # exit codes. If you're feeling risky, disable this safety check and use + # those default values. + if(GIT_FAIL_IF_NONZERO_EXIT ) + string(REPLACE ";" " " args_with_spaces "${ARGV}") + message(FATAL_ERROR "${stderr} (${GIT_EXECUTABLE} ${args_with_spaces})") + endif() + endif() +endmacro() + + + +# Function: GetGitState +# Description: gets the current state of the git repo. +# Args: +# _working_dir (in) string; the directory from which git commands will be executed. +function(GetGitState _working_dir) + + # This is an error code that'll be set to FALSE if the + # RunGitCommand ever returns a non-zero exit code. + set(ENV{GIT_RETRIEVED_STATE} "true") + + # Get whether or not the working tree is dirty. + if (GIT_IGNORE_UNTRACKED) + set(untracked_flag "-uno") + else() + set(untracked_flag "-unormal") + endif() + RunGitCommand(status --porcelain ${untracked_flag}) + if(NOT exit_code EQUAL 0) + set(ENV{GIT_IS_DIRTY} "false") + else() + if(NOT "${output}" STREQUAL "") + set(ENV{GIT_IS_DIRTY} "true") + else() + set(ENV{GIT_IS_DIRTY} "false") + endif() + endif() + + # There's a long list of attributes grabbed from git show. + set(object HEAD) + RunGitCommand(show -s "--format=%H" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_HEAD_SHA1} ${output}) + endif() + + RunGitCommand(show -s "--format=%an" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_AUTHOR_NAME} "${output}") + endif() + + RunGitCommand(show -s "--format=%ae" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_AUTHOR_EMAIL} "${output}") + endif() + + RunGitCommand(show -s "--format=%ci" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_COMMIT_DATE_ISO8601} "${output}") + endif() + + RunGitCommand(show -s "--format=%s" ${object}) + if(exit_code EQUAL 0) + # Escape \ + string(REPLACE "\\" "\\\\" output "${output}") + # Escape quotes + string(REPLACE "\"" "\\\"" output "${output}") + set(ENV{GIT_COMMIT_SUBJECT} "${output}") + endif() + + RunGitCommand(show -s "--format=%b" ${object}) + if(exit_code EQUAL 0) + if(output) + # Escape \ + string(REPLACE "\\" "\\\\" output "${output}") + # Escape quotes + string(REPLACE "\"" "\\\"" output "${output}") + # Escape line breaks in the commit message. + string(REPLACE "\r\n" "\\r\\n\\\r\n" safe "${output}") + if(safe STREQUAL output) + # Didn't have windows lines - try unix lines. + string(REPLACE "\n" "\\n\\\n" safe "${output}") + endif() + else() + # There was no commit body - set the safe string to empty. + set(safe "") + endif() + set(ENV{GIT_COMMIT_BODY} "${safe}") + else() + set(ENV{GIT_COMMIT_BODY} "") # empty string. + endif() + + # Get output of git describe + RunGitCommand(describe --always ${object}) + if(NOT exit_code EQUAL 0) + set(ENV{GIT_DESCRIBE} "unknown") + else() + set(ENV{GIT_DESCRIBE} "${output}") + endif() + + # Convert HEAD to a symbolic ref. This can fail, in which case we just + # set that variable to HEAD. + set(_permit_git_failure ON) + RunGitCommand(symbolic-ref --short -q ${object}) + unset(_permit_git_failure) + if(NOT exit_code EQUAL 0) + set(ENV{GIT_BRANCH} "${object}") + else() + set(ENV{GIT_BRANCH} "${output}") + endif() + + # >>> + # 2. Additional git properties can be added here via the + # "execute_process()" command. Be sure to set them in + # the environment using the same variable name you added + # to the "_state_variable_names" list. + +endfunction() + + + +# Function: GitStateChangedAction +# Description: this function is executed when the state of the git +# repository changes (e.g. a commit is made). +function(GitStateChangedAction) + foreach(var_name ${_state_variable_names}) + set(${var_name} $ENV{${var_name}}) + endforeach() + configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY) +endfunction() + + + +# Function: HashGitState +# Description: loop through the git state variables and compute a unique hash. +# Args: +# _state (out) string; a hash computed from the current git state. +function(HashGitState _state) + set(ans "") + foreach(var_name ${_state_variable_names}) + string(SHA256 ans "${ans}$ENV{${var_name}}") + endforeach() + set(${_state} ${ans} PARENT_SCOPE) +endfunction() + + + +# Function: CheckGit +# Description: check if the git repo has changed. If so, update the state file. +# Args: +# _working_dir (in) string; the directory from which git commands will be ran. +# _state_changed (out) bool; whether or no the state of the repo has changed. +function(CheckGit _working_dir _state_changed) + + # Get the current state of the repo. + GetGitState("${_working_dir}") + + # Convert that state into a hash that we can compare against + # the hash stored on-disk. + HashGitState(state) + + # Issue 14: post-configure file isn't being regenerated. + # + # Update the state to include the SHA256 for the pre-configure file. + # This forces the post-configure file to be regenerated if the + # pre-configure file has changed. + file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash) + string(SHA256 state "${preconfig_hash}${state}") + + # Check if the state has changed compared to the backup on disk. + if(EXISTS "${GIT_STATE_FILE}") + file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS) + if(OLD_HEAD_CONTENTS STREQUAL "${state}") + # State didn't change. + set(${_state_changed} "false" PARENT_SCOPE) + return() + endif() + endif() + + # The state has changed. + # We need to update the state file on disk. + # Future builds will compare their state to this file. + file(WRITE "${GIT_STATE_FILE}" "${state}") + set(${_state_changed} "true" PARENT_SCOPE) +endfunction() + + + +# Function: SetupGitMonitoring +# Description: this function sets up custom commands that make the build system +# check the state of git before every build. If the state has +# changed, then a file is configured. +function(SetupGitMonitoring) + add_custom_target(check_git + ALL + DEPENDS ${PRE_CONFIGURE_FILE} + BYPRODUCTS + ${POST_CONFIGURE_FILE} + ${GIT_STATE_FILE} + COMMENT "Checking the git repository for changes..." + COMMAND + ${CMAKE_COMMAND} + -D_BUILD_TIME_CHECK_GIT=TRUE + -DGIT_WORKING_DIR=${GIT_WORKING_DIR} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_STATE_FILE=${GIT_STATE_FILE} + -DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE} + -DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE} + -DGIT_FAIL_IF_NONZERO_EXIT=${GIT_FAIL_IF_NONZERO_EXIT} + -DGIT_IGNORE_UNTRACKED=${GIT_IGNORE_UNTRACKED} + -P "${CMAKE_CURRENT_LIST_FILE}") +endfunction() + + + +# Function: Main +# Description: primary entry-point to the script. Functions are selected based +# on whether it's configure or build time. +function(Main) + if(_BUILD_TIME_CHECK_GIT) + # Check if the repo has changed. + # If so, run the change action. + CheckGit("${GIT_WORKING_DIR}" changed) + if(changed OR NOT EXISTS "${POST_CONFIGURE_FILE}") + GitStateChangedAction() + endif() + else() + # >> Executes at configure time. + SetupGitMonitoring() + endif() +endfunction() + +# And off we go... +Main() diff --git a/flake.nix b/flake.nix index 37d465c0..ab5333da 100644 --- a/flake.nix +++ b/flake.nix @@ -8,6 +8,68 @@ supportedSystems = [ "aarch64-darwin" "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; in { + packages = forAllSystems (system: + let + pkgs = import nixpkgs { system = system; config.allowUnfree = true; }; + apple_sdk = pkgs.apple-sdk_15; + py = pkgs.python313; + in { + default = pkgs.stdenv.mkDerivation { + pname = "roofer"; + version = "1.0.0"; + + src = ./.; + + nativeBuildInputs = with pkgs; [ + cmake + ninja + py + uv + git + cacert + ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; + + buildInputs = with pkgs; [ + # roofer deps + cgal + gmp + mpfr + boost + eigen + fmt + + # apps + mimalloc + gdal + nlohmann_json + LAStools + + # python tools + geos # for shapely + ] ++ lib.optionals stdenv.isDarwin [ apple_sdk ]; + + cmakeFlags = [ + "-DCMAKE_BUILD_TYPE=Release" + "-DRF_BUILD_APPS=ON" + "-DRF_BUILD_BINDINGS=OFF" + "-DRF_BUILD_TESTING=OFF" + ]; + + preConfigure = '' + export pybind11_DIR="$(${py}/bin/python -c "import pybind11; print(pybind11.get_cmake_dir())")" + ''; + + hardeningDisable = [ "fortify" ]; + + meta = with pkgs.lib; { + description = "3D building reconstruction from point clouds"; + homepage = "https://github.com/3DBAG/roofer"; + license = licenses.lgpl3; + platforms = platforms.unix; + }; + }; + }); + devShells = forAllSystems (system: let pkgs = import nixpkgs { system = system; config.allowUnfree = true; }; From e0202f5bb574cfb4aa354660320acf5a124989e2 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 25 Aug 2025 15:42:12 +0200 Subject: [PATCH 04/53] use official nixpkgs-unstable --- flake.lock | 12 ++++++------ flake.nix | 2 +- uv.lock | 6 +++++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index 9b903ee0..96de48bd 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1754487508, - "narHash": "sha256-N3q2RRuJZk66yHEoT8SyxLSgR2t5/PYQPMU4nloapk4=", - "owner": "Ylannl", + "lastModified": 1756035328, + "narHash": "sha256-vC7SslUBCtdT3T37ZH3PLIWYmTkSeppL5BJJByUjYCM=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "2ffbaa27f2ab094eda2f1e375c3349a3ef569095", + "rev": "6b0b1559e918d4f7d1df398ee1d33aeac586d4d6", "type": "github" }, "original": { - "owner": "Ylannl", - "ref": "cgal", + "owner": "nixos", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index ab5333da..94e9210e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Development environment for Roofer"; - inputs.nixpkgs.url = "github:Ylannl/nixpkgs/cgal"; + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; outputs = { nixpkgs, ... }: let diff --git a/uv.lock b/uv.lock index 3d317f9f..3073bfc2 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13" [[package]] @@ -614,7 +614,11 @@ wheels = [ [[package]] name = "roofer" +<<<<<<< HEAD version = "1.0.0b5" +======= +version = "1.0.0b3" +>>>>>>> 0c41ba1 (use official nixpkgs-unstable) source = { virtual = "." } dependencies = [ { name = "breathe" }, From 496615fa19cd2836d588d115585f58ac6a9f2cce Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 29 Aug 2025 17:15:27 +0200 Subject: [PATCH 05/53] simplify version reporting --- CMakeLists.txt | 2 +- apps/CMakeLists.txt | 1 - .../cmake-git-version-tracking/CMakeLists.txt | 23 -- apps/cmake-git-version-tracking/LICENSE | 21 - apps/cmake-git-version-tracking/README.md | 65 ---- apps/cmake-git-version-tracking/git.c.in | 32 -- apps/cmake-git-version-tracking/git.h | 149 ------- .../git_watcher.cmake | 367 ------------------ apps/roofer-app/CMakeLists.txt | 4 +- apps/roofer-app/config.hpp | 9 +- apps/roofer-app/version.hpp | 4 + tests/CMakeLists.txt | 3 +- 12 files changed, 11 insertions(+), 669 deletions(-) delete mode 100644 apps/cmake-git-version-tracking/CMakeLists.txt delete mode 100644 apps/cmake-git-version-tracking/LICENSE delete mode 100644 apps/cmake-git-version-tracking/README.md delete mode 100644 apps/cmake-git-version-tracking/git.c.in delete mode 100644 apps/cmake-git-version-tracking/git.h delete mode 100644 apps/cmake-git-version-tracking/git_watcher.cmake create mode 100644 apps/roofer-app/version.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 03f0cbf0..20a95a8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ else() endif() # We have to use CPM (or FetchContent) even with vcpkg, because of -# cmake-git-version-tracking, val3dity, rerun +# val3dity, rerun include(CPM) set(GIT_IGNORE_UNTRACKED TRUE) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index a14dc34e..b24cd912 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,6 +1,5 @@ include_directories("${CMAKE_CURRENT_SOURCE_DIR}/external") -add_subdirectory(cmake-git-version-tracking) add_subdirectory(roofer-app) # add polyscope diff --git a/apps/cmake-git-version-tracking/CMakeLists.txt b/apps/cmake-git-version-tracking/CMakeLists.txt deleted file mode 100644 index 5a5cd69f..00000000 --- a/apps/cmake-git-version-tracking/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -cmake_minimum_required(VERSION 3.2...3.27) -project(cmake_git_version_tracking - LANGUAGES C) - -# Define the two required variables before including -# the source code for watching a git repository. -set(PRE_CONFIGURE_FILE "git.c.in") -set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git.c") -include(git_watcher.cmake) - -# Create a library out of the compiled post-configure file. -# -# Note that the include is a system include. This was done -# so downstream projects don't suffer from warnings on a -# 3rdparty library. -add_library(${PROJECT_NAME} STATIC ${POST_CONFIGURE_FILE}) -target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -add_dependencies(${PROJECT_NAME} check_git) - -# The C99 standard is only required because we're using . -# This could be removed if it's a problem for users, but would require the -# cmake configure() commands to translate true/false literals to 1/0. -set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) diff --git a/apps/cmake-git-version-tracking/LICENSE b/apps/cmake-git-version-tracking/LICENSE deleted file mode 100644 index b0af5790..00000000 --- a/apps/cmake-git-version-tracking/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Andrew Hardin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/apps/cmake-git-version-tracking/README.md b/apps/cmake-git-version-tracking/README.md deleted file mode 100644 index d7506f9c..00000000 --- a/apps/cmake-git-version-tracking/README.md +++ /dev/null @@ -1,65 +0,0 @@ -[![Regression Tests](https://github.com/andrew-hardin/cmake-git-version-tracking/actions/workflows/main.yml/badge.svg)](https://github.com/andrew-hardin/cmake-git-version-tracking/actions/workflows/main.yml) -# Embed Git metadata in C/C++ projects via CMake -This project embeds up-to-date git metadata in a standalone C/C++ static library via CMake. -It's written responsibly to only trigger rebuilds if git metadata changes (e.g. a new commit is added). -The core capability is baked into single self-contained -[script](git_watcher.cmake). - -## Requirements -- CMake >= 3.2 -- C Compiler (with C99 standard support) -- Git - -## Quickstart via FetchContent -You can use CMake's `FetchContent` module to build the static library `cmake_git_version_tracking`: -```cmake -include(FetchContent) -FetchContent_Declare(cmake_git_version_tracking - GIT_REPOSITORY https://github.com/andrew-hardin/cmake-git-version-tracking.git - GIT_TAG 904dbda1336ba4b9a1415a68d5f203f576b696bb -) -FetchContent_MakeAvailable(cmake_git_version_tracking) - -target_link_libraries(your_target - cmake_git_version_tracking -) -``` -Then [`#include git.h`](./git.h) and use the provided functions to retrieve git metadata. - -## Intended use case -You're continuously shipping prebuilt binaries for an -application. A user discovers a bug and files a bug report. -By embedding up-to-date versioning information, the user -can include this information in their report, e.g.: - -``` -Commit SHA1: 46a396e (46a396e6c1eb3d) -Dirty: false (there were no uncommitted changes at time of build) -``` - -This allows you to investigate the _precise_ version of the -application that the bug was reported in. - -## Q: What if I want to track `$special_git_field`? -Fork the project and modify [git_watcher.cmake](git_watcher.cmake) -to track new additional fields (e.g. kernel version or build hostname). -Sections that need to be modified are marked with `>>>`. - -## Q: Doesn't this already exist? -It depends on your specific requirements. Before writing this, I -found two categories of existing solutions: - -- Write the commit ID to the header at configure time (e.g. `cmake `). - This works well for automated build processes (e.g. check-in code and build artifacts). - However, any changes made after running `cmake` - (e.g. `git commit -am "Changed X"`) aren't reflected in the header. - -- Every time a build is started (e.g. `make`), write the commit ID to a header. - The major drawback of this method is that any object file that includes the new - header will be recompiled -- _even if the state of the git repo hasn't changed_. - -## Q: What's the better solution? -We check Git every time a build is started (e.g. `make`) to see if anything has changed, -like a new commit to the current branch. If nothing has changed, then we don't -touch anything- _no recompiling or linking is triggered_. If something has changed, then we -reconfigure the header and CMake rebuilds any downstream dependencies. diff --git a/apps/cmake-git-version-tracking/git.c.in b/apps/cmake-git-version-tracking/git.c.in deleted file mode 100644 index a26d27c5..00000000 --- a/apps/cmake-git-version-tracking/git.c.in +++ /dev/null @@ -1,32 +0,0 @@ -#include "git.h" - -bool git_IsPopulated() { - return @GIT_RETRIEVED_STATE@; -} -bool git_AnyUncommittedChanges() { - return @GIT_IS_DIRTY@; -} -const char* git_AuthorName() { - return "@GIT_AUTHOR_NAME@"; -} -const char* git_AuthorEmail() { - return "@GIT_AUTHOR_EMAIL@"; -} -const char* git_CommitSHA1() { - return "@GIT_HEAD_SHA1@"; -} -const char* git_CommitDate() { - return "@GIT_COMMIT_DATE_ISO8601@"; -} -const char* git_CommitSubject() { - return "@GIT_COMMIT_SUBJECT@"; -} -const char* git_CommitBody() { - return "@GIT_COMMIT_BODY@"; -} -const char* git_Describe() { - return "@GIT_DESCRIBE@"; -} -const char* git_Branch() { - return "@GIT_BRANCH@"; -} diff --git a/apps/cmake-git-version-tracking/git.h b/apps/cmake-git-version-tracking/git.h deleted file mode 100644 index ba95b2c9..00000000 --- a/apps/cmake-git-version-tracking/git.h +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once -// git.h -// https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git.h -// -// Released under the MIT License. -// https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE - -#include - -#ifdef __cplusplus -#define GIT_VERSION_TRACKING_EXTERN_C_BEGIN extern "C" { -#define GIT_VERSION_TRACKING_EXTERN_C_END } -#else -#define GIT_VERSION_TRACKING_EXTERN_C_BEGIN -#define GIT_VERSION_TRACKING_EXTERN_C_END -#endif - -// Don't mangle the C function names if included in a CXX file. -GIT_VERSION_TRACKING_EXTERN_C_BEGIN - -/// Is the metadata populated? -// -/// We may not have metadata if there wasn't a .git directory -/// (e.g. downloaded source code without revision history). -bool git_IsPopulated(); - -/// Were there any uncommitted changes that won't be reflected -/// in the CommitID? -bool git_AnyUncommittedChanges(); - -/// The commit author's name. -const char* git_AuthorName(); - -/// The commit author's email. -const char* git_AuthorEmail(); - -/// The commit SHA1. -const char* git_CommitSHA1(); - -/// The ISO8601 commit date. -const char* git_CommitDate(); - -/// The commit subject. -const char* git_CommitSubject(); - -/// The commit body. -const char* git_CommitBody(); - -/// The commit describe. -const char* git_Describe(); - -/// The symbolic reference tied to HEAD. -const char* git_Branch(); - -GIT_VERSION_TRACKING_EXTERN_C_END -#undef GIT_VERSION_TRACKING_EXTERN_C_BEGIN -#undef GIT_VERSION_TRACKING_EXTERN_C_END - -#ifdef __cplusplus - -/// This is a utility extension for C++ projects. -/// It provides a "git" namespace that wraps the -/// C methods in more(?) ergonomic types. -/// -/// This is header-only in an effort to keep the -/// underlying static library C99 compliant. - -// We really want to use std::string_view if it appears -// that the compiler will support it. If that fails, -// revert back to std::string. -#define GIT_VERSION_TRACKING_CPP_17_STANDARD 201703L -#if __cplusplus >= GIT_VERSION_TRACKING_CPP_17_STANDARD -#define GIT_VERSION_USE_STRING_VIEW 1 -#else -#define GIT_VERSION_USE_STRING_VIEW 0 -#endif - -#if GIT_VERSION_USE_STRING_VIEW -#include -#include -#else -#include -#endif - -namespace git { - -#if GIT_VERSION_USE_STRING_VIEW - using StringOrView = std::string_view; -#else - typedef std::string StringOrView; -#endif - - namespace internal { - - /// Short-hand method for initializing a std::string or std::string_view - /// given a C-style const char*. - inline const StringOrView InitString(const char* from_c_interface) { -#if GIT_VERSION_USE_STRING_VIEW - return StringOrView{from_c_interface, std::strlen(from_c_interface)}; -#else - return std::string(from_c_interface); -#endif - } - - } // namespace internal - - inline bool IsPopulated() { return git_IsPopulated(); } - inline bool AnyUncommittedChanges() { return git_AnyUncommittedChanges(); } - inline const StringOrView& AuthorName() { - static const StringOrView kValue = internal::InitString(git_AuthorName()); - return kValue; - } - inline const StringOrView AuthorEmail() { - static const StringOrView kValue = internal::InitString(git_AuthorEmail()); - return kValue; - } - inline const StringOrView CommitSHA1() { - static const StringOrView kValue = internal::InitString(git_CommitSHA1()); - return kValue; - } - inline const StringOrView CommitDate() { - static const StringOrView kValue = internal::InitString(git_CommitDate()); - return kValue; - } - inline const StringOrView CommitSubject() { - static const StringOrView kValue = - internal::InitString(git_CommitSubject()); - return kValue; - } - inline const StringOrView CommitBody() { - static const StringOrView kValue = internal::InitString(git_CommitBody()); - return kValue; - } - inline const StringOrView Describe() { - static const StringOrView kValue = internal::InitString(git_Describe()); - return kValue; - } - inline const StringOrView Branch() { - static const StringOrView kValue = internal::InitString(git_Branch()); - return kValue; - } - -} // namespace git - -// Cleanup our defines to avoid polluting. -#undef GIT_VERSION_USE_STRING_VIEW -#undef GIT_VERSION_TRACKING_CPP_17_STANDARD - -#endif // __cplusplus diff --git a/apps/cmake-git-version-tracking/git_watcher.cmake b/apps/cmake-git-version-tracking/git_watcher.cmake deleted file mode 100644 index f69295a3..00000000 --- a/apps/cmake-git-version-tracking/git_watcher.cmake +++ /dev/null @@ -1,367 +0,0 @@ -# git_watcher.cmake -# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake -# -# Released under the MIT License. -# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE - - -# This file defines a target that monitors the state of a git repo. -# If the state changes (e.g. a commit is made), then a file gets reconfigured. -# Here are the primary variables that control script behavior: -# -# PRE_CONFIGURE_FILE (REQUIRED) -# -- The path to the file that'll be configured. -# -# POST_CONFIGURE_FILE (REQUIRED) -# -- The path to the configured PRE_CONFIGURE_FILE. -# -# GIT_STATE_FILE (OPTIONAL) -# -- The path to the file used to store the previous build's git state. -# Defaults to the current binary directory. -# -# GIT_WORKING_DIR (OPTIONAL) -# -- The directory from which git commands will be run. -# Defaults to the directory with the top level CMakeLists.txt. -# -# GIT_EXECUTABLE (OPTIONAL) -# -- The path to the git executable. It'll automatically be set if the -# user doesn't supply a path. -# -# GIT_FAIL_IF_NONZERO_EXIT (OPTIONAL) -# -- Raise a FATAL_ERROR if any of the git commands return a non-zero -# exit code. This is set to TRUE by default. You can set this to FALSE -# if you'd like the build to continue even if a git command fails. -# -# GIT_IGNORE_UNTRACKED (OPTIONAL) -# -- Ignore the presence of untracked files when detecting if the -# working tree is dirty. This is set to FALSE by default. -# -# DESIGN -# - This script was designed similar to a Python application -# with a Main() function. I wanted to keep it compact to -# simplify "copy + paste" usage. -# -# - This script is invoked under two CMake contexts: -# 1. Configure time (when build files are created). -# 2. Build time (called via CMake -P). -# The first invocation is what registers the script to -# be executed at build time. -# -# MODIFICATIONS -# You may wish to track other git properties like when the last -# commit was made. There are two sections you need to modify, -# and they're tagged with a ">>>" header. - -# Short hand for converting paths to absolute. -macro(PATH_TO_ABSOLUTE var_name) - get_filename_component(${var_name} "${${var_name}}" ABSOLUTE) -endmacro() - -# Check that a required variable is set. -macro(CHECK_REQUIRED_VARIABLE var_name) - if(NOT DEFINED ${var_name}) - message(FATAL_ERROR "The \"${var_name}\" variable must be defined.") - endif() - PATH_TO_ABSOLUTE(${var_name}) -endmacro() - -# Check that an optional variable is set, or, set it to a default value. -macro(CHECK_OPTIONAL_VARIABLE_NOPATH var_name default_value) - if(NOT DEFINED ${var_name}) - set(${var_name} ${default_value}) - endif() -endmacro() - -# Check that an optional variable is set, or, set it to a default value. -# Also converts that path to an abspath. -macro(CHECK_OPTIONAL_VARIABLE var_name default_value) - CHECK_OPTIONAL_VARIABLE_NOPATH(${var_name} ${default_value}) - PATH_TO_ABSOLUTE(${var_name}) -endmacro() - -CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE) -CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE) -CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git-state-hash") -CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}") -CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_FAIL_IF_NONZERO_EXIT TRUE) -CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_IGNORE_UNTRACKED FALSE) - -# Check the optional git variable. -# If it's not set, we'll try to find it using the CMake packaging system. -if(NOT DEFINED GIT_EXECUTABLE) - find_package(Git QUIET REQUIRED) -endif() -CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE) - - -set(_state_variable_names - GIT_RETRIEVED_STATE - GIT_HEAD_SHA1 - GIT_IS_DIRTY - GIT_AUTHOR_NAME - GIT_AUTHOR_EMAIL - GIT_COMMIT_DATE_ISO8601 - GIT_COMMIT_SUBJECT - GIT_COMMIT_BODY - GIT_DESCRIBE - GIT_BRANCH - # >>> - # 1. Add the name of the additional git variable you're interested in monitoring - # to this list. -) - - - -# Macro: RunGitCommand -# Description: short-hand macro for calling a git function. Outputs are the -# "exit_code" and "output" variables. The "_permit_git_failure" -# variable can locally override the exit code checking- use it -# with caution. -macro(RunGitCommand) - execute_process(COMMAND - "${GIT_EXECUTABLE}" ${ARGV} - WORKING_DIRECTORY "${_working_dir}" - RESULT_VARIABLE exit_code - OUTPUT_VARIABLE output - ERROR_VARIABLE stderr - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT exit_code EQUAL 0 AND NOT _permit_git_failure) - set(ENV{GIT_RETRIEVED_STATE} "false") - - # Issue 26: git info not properly set - # - # Check if we should fail if any of the exit codes are non-zero. - # Most methods have a fall-back default value that's used in case of non-zero - # exit codes. If you're feeling risky, disable this safety check and use - # those default values. - if(GIT_FAIL_IF_NONZERO_EXIT ) - string(REPLACE ";" " " args_with_spaces "${ARGV}") - message(FATAL_ERROR "${stderr} (${GIT_EXECUTABLE} ${args_with_spaces})") - endif() - endif() -endmacro() - - - -# Function: GetGitState -# Description: gets the current state of the git repo. -# Args: -# _working_dir (in) string; the directory from which git commands will be executed. -function(GetGitState _working_dir) - - # This is an error code that'll be set to FALSE if the - # RunGitCommand ever returns a non-zero exit code. - set(ENV{GIT_RETRIEVED_STATE} "true") - - # Get whether or not the working tree is dirty. - if (GIT_IGNORE_UNTRACKED) - set(untracked_flag "-uno") - else() - set(untracked_flag "-unormal") - endif() - RunGitCommand(status --porcelain ${untracked_flag}) - if(NOT exit_code EQUAL 0) - set(ENV{GIT_IS_DIRTY} "false") - else() - if(NOT "${output}" STREQUAL "") - set(ENV{GIT_IS_DIRTY} "true") - else() - set(ENV{GIT_IS_DIRTY} "false") - endif() - endif() - - # There's a long list of attributes grabbed from git show. - set(object HEAD) - RunGitCommand(show -s "--format=%H" ${object}) - if(exit_code EQUAL 0) - set(ENV{GIT_HEAD_SHA1} ${output}) - endif() - - RunGitCommand(show -s "--format=%an" ${object}) - if(exit_code EQUAL 0) - set(ENV{GIT_AUTHOR_NAME} "${output}") - endif() - - RunGitCommand(show -s "--format=%ae" ${object}) - if(exit_code EQUAL 0) - set(ENV{GIT_AUTHOR_EMAIL} "${output}") - endif() - - RunGitCommand(show -s "--format=%ci" ${object}) - if(exit_code EQUAL 0) - set(ENV{GIT_COMMIT_DATE_ISO8601} "${output}") - endif() - - RunGitCommand(show -s "--format=%s" ${object}) - if(exit_code EQUAL 0) - # Escape \ - string(REPLACE "\\" "\\\\" output "${output}") - # Escape quotes - string(REPLACE "\"" "\\\"" output "${output}") - set(ENV{GIT_COMMIT_SUBJECT} "${output}") - endif() - - RunGitCommand(show -s "--format=%b" ${object}) - if(exit_code EQUAL 0) - if(output) - # Escape \ - string(REPLACE "\\" "\\\\" output "${output}") - # Escape quotes - string(REPLACE "\"" "\\\"" output "${output}") - # Escape line breaks in the commit message. - string(REPLACE "\r\n" "\\r\\n\\\r\n" safe "${output}") - if(safe STREQUAL output) - # Didn't have windows lines - try unix lines. - string(REPLACE "\n" "\\n\\\n" safe "${output}") - endif() - else() - # There was no commit body - set the safe string to empty. - set(safe "") - endif() - set(ENV{GIT_COMMIT_BODY} "${safe}") - else() - set(ENV{GIT_COMMIT_BODY} "") # empty string. - endif() - - # Get output of git describe - RunGitCommand(describe --always ${object}) - if(NOT exit_code EQUAL 0) - set(ENV{GIT_DESCRIBE} "unknown") - else() - set(ENV{GIT_DESCRIBE} "${output}") - endif() - - # Convert HEAD to a symbolic ref. This can fail, in which case we just - # set that variable to HEAD. - set(_permit_git_failure ON) - RunGitCommand(symbolic-ref --short -q ${object}) - unset(_permit_git_failure) - if(NOT exit_code EQUAL 0) - set(ENV{GIT_BRANCH} "${object}") - else() - set(ENV{GIT_BRANCH} "${output}") - endif() - - # >>> - # 2. Additional git properties can be added here via the - # "execute_process()" command. Be sure to set them in - # the environment using the same variable name you added - # to the "_state_variable_names" list. - -endfunction() - - - -# Function: GitStateChangedAction -# Description: this function is executed when the state of the git -# repository changes (e.g. a commit is made). -function(GitStateChangedAction) - foreach(var_name ${_state_variable_names}) - set(${var_name} $ENV{${var_name}}) - endforeach() - configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY) -endfunction() - - - -# Function: HashGitState -# Description: loop through the git state variables and compute a unique hash. -# Args: -# _state (out) string; a hash computed from the current git state. -function(HashGitState _state) - set(ans "") - foreach(var_name ${_state_variable_names}) - string(SHA256 ans "${ans}$ENV{${var_name}}") - endforeach() - set(${_state} ${ans} PARENT_SCOPE) -endfunction() - - - -# Function: CheckGit -# Description: check if the git repo has changed. If so, update the state file. -# Args: -# _working_dir (in) string; the directory from which git commands will be ran. -# _state_changed (out) bool; whether or no the state of the repo has changed. -function(CheckGit _working_dir _state_changed) - - # Get the current state of the repo. - GetGitState("${_working_dir}") - - # Convert that state into a hash that we can compare against - # the hash stored on-disk. - HashGitState(state) - - # Issue 14: post-configure file isn't being regenerated. - # - # Update the state to include the SHA256 for the pre-configure file. - # This forces the post-configure file to be regenerated if the - # pre-configure file has changed. - file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash) - string(SHA256 state "${preconfig_hash}${state}") - - # Check if the state has changed compared to the backup on disk. - if(EXISTS "${GIT_STATE_FILE}") - file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS) - if(OLD_HEAD_CONTENTS STREQUAL "${state}") - # State didn't change. - set(${_state_changed} "false" PARENT_SCOPE) - return() - endif() - endif() - - # The state has changed. - # We need to update the state file on disk. - # Future builds will compare their state to this file. - file(WRITE "${GIT_STATE_FILE}" "${state}") - set(${_state_changed} "true" PARENT_SCOPE) -endfunction() - - - -# Function: SetupGitMonitoring -# Description: this function sets up custom commands that make the build system -# check the state of git before every build. If the state has -# changed, then a file is configured. -function(SetupGitMonitoring) - add_custom_target(check_git - ALL - DEPENDS ${PRE_CONFIGURE_FILE} - BYPRODUCTS - ${POST_CONFIGURE_FILE} - ${GIT_STATE_FILE} - COMMENT "Checking the git repository for changes..." - COMMAND - ${CMAKE_COMMAND} - -D_BUILD_TIME_CHECK_GIT=TRUE - -DGIT_WORKING_DIR=${GIT_WORKING_DIR} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -DGIT_STATE_FILE=${GIT_STATE_FILE} - -DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE} - -DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE} - -DGIT_FAIL_IF_NONZERO_EXIT=${GIT_FAIL_IF_NONZERO_EXIT} - -DGIT_IGNORE_UNTRACKED=${GIT_IGNORE_UNTRACKED} - -P "${CMAKE_CURRENT_LIST_FILE}") -endfunction() - - - -# Function: Main -# Description: primary entry-point to the script. Functions are selected based -# on whether it's configure or build time. -function(Main) - if(_BUILD_TIME_CHECK_GIT) - # Check if the repo has changed. - # If so, run the change action. - CheckGit("${GIT_WORKING_DIR}" changed) - if(changed OR NOT EXISTS "${POST_CONFIGURE_FILE}") - GitStateChangedAction() - endif() - else() - # >> Executes at configure time. - SetupGitMonitoring() - endif() -endfunction() - -# And off we go... -Main() diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index ba9fb857..96cc63e9 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -1,7 +1,7 @@ if(RF_BUILD_APPS) set(APP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/roofer-app.cpp") - set(ROOFER_LINK_LIBRARIES roofer-extra fmt::fmt cmake_git_version_tracking) + set(ROOFER_LINK_LIBRARIES roofer-extra fmt::fmt) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") find_package(mimalloc CONFIG REQUIRED) @@ -56,7 +56,7 @@ endif() if(RF_BUILD_DOC_HELPER OR RF_BUILD_APPS) add_executable("doc-helper" "doc-helper.cpp") set_target_properties("doc-helper" PROPERTIES CXX_STANDARD 20) - target_link_libraries("doc-helper" PRIVATE roofer-core fmt::fmt cmake_git_version_tracking) + target_link_libraries("doc-helper" PRIVATE roofer-core fmt::fmt) # install( # TARGETS "doc-helper" # ARCHIVE DESTINATION lib diff --git a/apps/roofer-app/config.hpp b/apps/roofer-app/config.hpp index e199a2e1..975c4fbe 100644 --- a/apps/roofer-app/config.hpp +++ b/apps/roofer-app/config.hpp @@ -38,7 +38,7 @@ #include #include #include -#include "git.h" +#include "version.hpp" namespace roofer::enums { enum TerrainStrategy { @@ -912,11 +912,8 @@ struct RooferConfigHandler { } void print_version() { - std::cout << std::format( - "roofer {} ({}{}{})\n", git_Describe(), - std::strcmp(git_Branch(), "main") ? "" - : std::format("{}, ", git_Branch()), - git_AnyUncommittedChanges() ? "dirty, " : "", git_CommitDate()); + std::cout << std::format("roofer {} (git ref: {})\n", RF_VERSION, + RF_GIT_HASH); } void parse_cli_first_pass(CLIArgs& c) { diff --git a/apps/roofer-app/version.hpp b/apps/roofer-app/version.hpp new file mode 100644 index 00000000..5bec3367 --- /dev/null +++ b/apps/roofer-app/version.hpp @@ -0,0 +1,4 @@ +#define RF_VERSION "1.0.0-beta.5" +#if not defined(RF_GIT_HASH) +#define RF_GIT_HASH "unknown" +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e3fe05f1..15e94062 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,8 +59,7 @@ include_directories("${PROJECT_SOURCE_DIR}/apps/external") add_executable("reconstruct_api" "${CMAKE_CURRENT_SOURCE_DIR}/test_reconstruct_api.cpp") set_target_properties("reconstruct_api" PROPERTIES CXX_STANDARD 20) -target_link_libraries("reconstruct_api" PRIVATE roofer-extra - cmake_git_version_tracking) +target_link_libraries("reconstruct_api" PRIVATE roofer-extra) add_test( NAME "reconstruct-api-wippolder" COMMAND $ From ce94c4a688ab53b4ace123953d07ef41c4ae1300 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 29 Aug 2025 17:57:28 +0200 Subject: [PATCH 06/53] first nix build success --- CMakeLists.txt | 4 ++++ apps/roofer-app/config.hpp | 3 +-- flake.nix | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20a95a8d..8aae1b90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,10 @@ endif() # TODO: add version number project(roofer VERSION 1.0.0 LANGUAGES C CXX) +if(RF_GIT_HASH) + add_compile_definitions(RF_GIT_HASH="${RF_GIT_HASH}") +endif() + # Global CMake variables are set here We use C++20, with the assumption that we # only implement features that are supported by GCC, Clang, MSVC, Apple Clang set(CMAKE_CXX_STANDARD 20) diff --git a/apps/roofer-app/config.hpp b/apps/roofer-app/config.hpp index 975c4fbe..c802f583 100644 --- a/apps/roofer-app/config.hpp +++ b/apps/roofer-app/config.hpp @@ -912,8 +912,7 @@ struct RooferConfigHandler { } void print_version() { - std::cout << std::format("roofer {} (git ref: {})\n", RF_VERSION, - RF_GIT_HASH); + std::cout << std::format("roofer {} ({})\n", RF_VERSION, RF_GIT_HASH); } void parse_cli_first_pass(CLIArgs& c) { diff --git a/flake.nix b/flake.nix index 94e9210e..a71ca126 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; - outputs = { nixpkgs, ... }: + outputs = { self, nixpkgs, ... }: let supportedSystems = [ "aarch64-darwin" "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; @@ -16,7 +16,7 @@ in { default = pkgs.stdenv.mkDerivation { pname = "roofer"; - version = "1.0.0"; + version = "1.0.0-beta.5"; src = ./.; @@ -25,7 +25,6 @@ ninja py uv - git cacert ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; @@ -53,6 +52,7 @@ "-DRF_BUILD_APPS=ON" "-DRF_BUILD_BINDINGS=OFF" "-DRF_BUILD_TESTING=OFF" + "-DRF_GIT_HASH=${self.dirtyShortRev}" ]; preConfigure = '' From 9581f0bc34f387a985a774f847c832101d98d741 Mon Sep 17 00:00:00 2001 From: Ylannl Date: Sun, 31 Aug 2025 22:42:35 +0200 Subject: [PATCH 07/53] nix: fix self.dirtyShortRev not found error --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index a71ca126..3bd8f614 100644 --- a/flake.nix +++ b/flake.nix @@ -13,6 +13,7 @@ pkgs = import nixpkgs { system = system; config.allowUnfree = true; }; apple_sdk = pkgs.apple-sdk_15; py = pkgs.python313; + shortRev = self.shortRev or self.dirtyShortRev or "unknown"; in { default = pkgs.stdenv.mkDerivation { pname = "roofer"; @@ -52,7 +53,7 @@ "-DRF_BUILD_APPS=ON" "-DRF_BUILD_BINDINGS=OFF" "-DRF_BUILD_TESTING=OFF" - "-DRF_GIT_HASH=${self.dirtyShortRev}" + "-DRF_GIT_HASH=${shortRev}" ]; preConfigure = '' From ca4f056a1337a8bf0f1f353e4e8762abd735b6df Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 1 Sep 2025 17:13:00 +0200 Subject: [PATCH 08/53] improve version cli reporting --- .bumpversion.toml | 11 +++++++++++ apps/roofer-app/config.hpp | 7 ++++++- apps/roofer-app/version.hpp | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.bumpversion.toml b/.bumpversion.toml index 00b4f083..b469ebe7 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -64,3 +64,14 @@ filename = "CMakeLists.txt" serialize = ["{major}.{minor}.{patch}"] search = "project(roofer VERSION {current_version}" replace = "project(roofer VERSION {new_version}" + +[[tool.bumpversion.files]] +filename = "apps/roofer-app/version.hpp" +search = "RF_VERSION \"{current_version}\"" +replace = "RF_VERSION \"{new_version}\"" + +[[tool.bumpversion.files]] +filename = "apps/roofer-app/version.hpp" +regex = true +search = '#define\s+RF_VERSION_GIT_HASH\s+"([^"]+)"' +replace = '#define RF_VERSION_GIT_HASH "{short_commit_sha}"' diff --git a/apps/roofer-app/config.hpp b/apps/roofer-app/config.hpp index c802f583..35b6955f 100644 --- a/apps/roofer-app/config.hpp +++ b/apps/roofer-app/config.hpp @@ -912,7 +912,12 @@ struct RooferConfigHandler { } void print_version() { - std::cout << std::format("roofer {} ({})\n", RF_VERSION, RF_GIT_HASH); + if (strcmp(RF_VERSION_GIT_HASH, RF_GIT_HASH) == 0) { + std::cout << std::format("roofer {} ({})\n", RF_VERSION, + RF_VERSION_GIT_HASH); + } else { + std::cout << std::format("roofer {}-dev ({})\n", RF_VERSION, RF_GIT_HASH); + } } void parse_cli_first_pass(CLIArgs& c) { diff --git a/apps/roofer-app/version.hpp b/apps/roofer-app/version.hpp index 5bec3367..33faa199 100644 --- a/apps/roofer-app/version.hpp +++ b/apps/roofer-app/version.hpp @@ -1,4 +1,5 @@ #define RF_VERSION "1.0.0-beta.5" +#define RF_VERSION_GIT_HASH "g6cdf35f" #if not defined(RF_GIT_HASH) #define RF_GIT_HASH "unknown" #endif From aa78a12c50384e6481d1add3b66798e0561f68f9 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 1 Sep 2025 17:48:11 +0200 Subject: [PATCH 09/53] auto bump version.hpp --- .bumpversion.toml | 8 +------- CMakeLists.txt | 3 +++ apps/roofer-app/config.hpp | 7 +------ apps/roofer-app/version.hpp | 1 - 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.bumpversion.toml b/.bumpversion.toml index b469ebe7..b10711ab 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,5 +1,5 @@ [tool.bumpversion] -current_version = "1.0.0-beta.5" +current_version = "1.0.0-beta.7" parse = """(?x) (?P0|[1-9]\\d*)\\. (?P0|[1-9]\\d*)\\. @@ -69,9 +69,3 @@ replace = "project(roofer VERSION {new_version}" filename = "apps/roofer-app/version.hpp" search = "RF_VERSION \"{current_version}\"" replace = "RF_VERSION \"{new_version}\"" - -[[tool.bumpversion.files]] -filename = "apps/roofer-app/version.hpp" -regex = true -search = '#define\s+RF_VERSION_GIT_HASH\s+"([^"]+)"' -replace = '#define RF_VERSION_GIT_HASH "{short_commit_sha}"' diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aae1b90..b0c28a16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,9 @@ project(roofer VERSION 1.0.0 LANGUAGES C CXX) if(RF_GIT_HASH) add_compile_definitions(RF_GIT_HASH="${RF_GIT_HASH}") +else() + execute_process(COMMAND git rev-parse --short HEAD OUTPUT_VARIABLE RF_GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) + add_compile_definitions(RF_GIT_HASH="${RF_GIT_HASH}") endif() # Global CMake variables are set here We use C++20, with the assumption that we diff --git a/apps/roofer-app/config.hpp b/apps/roofer-app/config.hpp index 35b6955f..c802f583 100644 --- a/apps/roofer-app/config.hpp +++ b/apps/roofer-app/config.hpp @@ -912,12 +912,7 @@ struct RooferConfigHandler { } void print_version() { - if (strcmp(RF_VERSION_GIT_HASH, RF_GIT_HASH) == 0) { - std::cout << std::format("roofer {} ({})\n", RF_VERSION, - RF_VERSION_GIT_HASH); - } else { - std::cout << std::format("roofer {}-dev ({})\n", RF_VERSION, RF_GIT_HASH); - } + std::cout << std::format("roofer {} ({})\n", RF_VERSION, RF_GIT_HASH); } void parse_cli_first_pass(CLIArgs& c) { diff --git a/apps/roofer-app/version.hpp b/apps/roofer-app/version.hpp index 33faa199..5bec3367 100644 --- a/apps/roofer-app/version.hpp +++ b/apps/roofer-app/version.hpp @@ -1,5 +1,4 @@ #define RF_VERSION "1.0.0-beta.5" -#define RF_VERSION_GIT_HASH "g6cdf35f" #if not defined(RF_GIT_HASH) #define RF_GIT_HASH "unknown" #endif From 59213b0e7a069bb59693d07366d89e4e9c139b12 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 1 Sep 2025 19:22:23 +0200 Subject: [PATCH 10/53] add cmake fallback for populating RF_GIT_HASH --- CMakeLists.txt | 7 ------- CMakePresets.json | 1 + apps/roofer-app/CMakeLists.txt | 31 ++++++++++++++++++++++++++++++- apps/roofer-app/version.hpp | 2 +- cmake/gen_git_hash_header.cmake | 9 +++++++++ 5 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 cmake/gen_git_hash_header.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b0c28a16..20a95a8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,13 +36,6 @@ endif() # TODO: add version number project(roofer VERSION 1.0.0 LANGUAGES C CXX) -if(RF_GIT_HASH) - add_compile_definitions(RF_GIT_HASH="${RF_GIT_HASH}") -else() - execute_process(COMMAND git rev-parse --short HEAD OUTPUT_VARIABLE RF_GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) - add_compile_definitions(RF_GIT_HASH="${RF_GIT_HASH}") -endif() - # Global CMake variables are set here We use C++20, with the assumption that we # only implement features that are supported by GCC, Clang, MSVC, Apple Clang set(CMAKE_CXX_STANDARD 20) diff --git a/CMakePresets.json b/CMakePresets.json index 5fd59049..f8093063 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -25,6 +25,7 @@ "RF_USE_LOGGER_SPDLOG": "OFF", "RF_BUILD_TESTING": "ON", "RF_BUILD_APPS": "ON", + "RF_BUILD_DOC_HELPER": "ON", "RF_BUILD_BINDINGS": "ON" } }, diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index 96cc63e9..85314a4c 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -53,7 +53,7 @@ if(RF_BUILD_APPS) endif() endif() -if(RF_BUILD_DOC_HELPER OR RF_BUILD_APPS) +if(RF_BUILD_DOC_HELPER) add_executable("doc-helper" "doc-helper.cpp") set_target_properties("doc-helper" PROPERTIES CXX_STANDARD 20) target_link_libraries("doc-helper" PRIVATE roofer-core fmt::fmt) @@ -63,3 +63,32 @@ if(RF_BUILD_DOC_HELPER OR RF_BUILD_APPS) # LIBRARY DESTINATION lib # RUNTIME DESTINATION bin) endif() + +# get the --version flag working. Try to use RF_GIT_HASH if defined (eg by nix build), otherwise fallback to cmake helper function +if(RF_BUILD_DOC_HELPER OR RF_BUILD_APPS) + if(RF_GIT_HASH) + target_compile_definitions(roofer PRIVATE RF_GIT_HASH="${RF_GIT_HASH}") + else() + set(GIT_HASH_HEADER ${CMAKE_BINARY_DIR}/git_hash.h) + + add_custom_command( + OUTPUT ${GIT_HASH_HEADER} + COMMAND ${CMAKE_COMMAND} -DOUT=${GIT_HASH_HEADER} -P ${CMAKE_SOURCE_DIR}/cmake/gen_git_hash_header.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating git_hash.h" + VERBATIM + ) + + add_custom_target(update_git_hash ALL DEPENDS ${GIT_HASH_HEADER}) + + if (RF_BUILD_APPS) + target_include_directories(roofer PRIVATE ${CMAKE_BINARY_DIR}) + add_dependencies(roofer update_git_hash) + endif() + + if (RF_BUILD_DOC_HELPER) + target_include_directories("doc-helper" PRIVATE ${CMAKE_BINARY_DIR}) + add_dependencies("doc-helper" update_git_hash) + endif() + endif() +endif() diff --git a/apps/roofer-app/version.hpp b/apps/roofer-app/version.hpp index 5bec3367..e0f7d4af 100644 --- a/apps/roofer-app/version.hpp +++ b/apps/roofer-app/version.hpp @@ -1,4 +1,4 @@ #define RF_VERSION "1.0.0-beta.5" #if not defined(RF_GIT_HASH) -#define RF_GIT_HASH "unknown" +#include "git_hash.h" #endif diff --git a/cmake/gen_git_hash_header.cmake b/cmake/gen_git_hash_header.cmake new file mode 100644 index 00000000..ae2c15d4 --- /dev/null +++ b/cmake/gen_git_hash_header.cmake @@ -0,0 +1,9 @@ +# gen_git_hash_header.cmake +execute_process( + COMMAND git describe --match=NeVeRmAtCh --always --dirty + OUTPUT_VARIABLE HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +file(WRITE ${OUT} "#pragma once\n") +file(APPEND ${OUT} "#define RF_GIT_HASH \"${HASH}\"\n") From 9e85496882183f93467f55a48625cd850275f5b2 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 1 Sep 2025 20:53:14 +0200 Subject: [PATCH 11/53] clean up a flake.nix --- flake.nix | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/flake.nix b/flake.nix index 3bd8f614..a24db761 100644 --- a/flake.nix +++ b/flake.nix @@ -30,28 +30,29 @@ ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; buildInputs = with pkgs; [ - # roofer deps + # core roofer deps cgal gmp mpfr boost eigen - fmt - # apps + # app deps mimalloc gdal nlohmann_json LAStools + geos + fmt - # python tools - geos # for shapely + # python binding deps + # python313Packages.pybind11 ] ++ lib.optionals stdenv.isDarwin [ apple_sdk ]; cmakeFlags = [ "-DCMAKE_BUILD_TYPE=Release" "-DRF_BUILD_APPS=ON" - "-DRF_BUILD_BINDINGS=OFF" + # "-DRF_BUILD_BINDINGS=ON" "-DRF_BUILD_TESTING=OFF" "-DRF_GIT_HASH=${shortRev}" ]; @@ -60,8 +61,6 @@ export pybind11_DIR="$(${py}/bin/python -c "import pybind11; print(pybind11.get_cmake_dir())")" ''; - hardeningDisable = [ "fortify" ]; - meta = with pkgs.lib; { description = "3D building reconstruction from point clouds"; homepage = "https://github.com/3DBAG/roofer"; @@ -106,11 +105,8 @@ # docs doxygen - ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ] - ++ lib.optionals (builtins.getEnv "GITHUB_ACTIONS" == "true") [mono]; # this is needed to make gh actions binary caching work with vcpkg + ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; - hardeningDisable = [ "fortify" ]; - VCPKG_ROOT = "${pkgs.vcpkg}/share/vcpkg"; UV_NO_BINARY = 1; # VCPKG_FORCE_SYSTEM_BINARIES = 1; scm_version = "unknown"; From 55235ecbe72c915f5c7280c58520a9bcaba751d2 Mon Sep 17 00:00:00 2001 From: Ylannl Date: Tue, 2 Sep 2025 17:52:44 +0200 Subject: [PATCH 12/53] nix develop: make build work with val3dity --- flake.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index a24db761..f8f39e80 100644 --- a/flake.nix +++ b/flake.nix @@ -84,24 +84,29 @@ cmakeCurses ninja - # roofer deps + # roofer core deps cgal gmp mpfr - boost + pkgsStatic.boost eigen fmt + # val3dity + spdlog + pugixml + tclap + # apps mimalloc gdal nlohmann_json LAStools + geos # python tools py uv - geos # for shapely # docs doxygen From 6a6721952cf014173bd901538dace055d921cdd6 Mon Sep 17 00:00:00 2001 From: Ylannl Date: Tue, 2 Sep 2025 18:44:31 +0200 Subject: [PATCH 13/53] further cleanup flake.nix --- flake.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index f8f39e80..67e038d2 100644 --- a/flake.nix +++ b/flake.nix @@ -23,10 +23,6 @@ nativeBuildInputs = with pkgs; [ cmake - ninja - py - uv - cacert ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; buildInputs = with pkgs; [ @@ -46,6 +42,7 @@ fmt # python binding deps + # py # python313Packages.pybind11 ] ++ lib.optionals stdenv.isDarwin [ apple_sdk ]; @@ -82,7 +79,6 @@ } { buildInputs = with pkgs; [ cmakeCurses - ninja # roofer core deps cgal From 29e7e378dd88e0120822768f88d113a452c8302c Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 3 Sep 2025 00:16:50 +0200 Subject: [PATCH 14/53] refine flake.nix --- flake.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 67e038d2..68baecfd 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,7 @@ nativeBuildInputs = with pkgs; [ cmake + ninja ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; buildInputs = with pkgs; [ @@ -35,7 +36,7 @@ # app deps mimalloc - gdal + pkgsStatic.gdalMinimal nlohmann_json LAStools geos @@ -52,6 +53,7 @@ # "-DRF_BUILD_BINDINGS=ON" "-DRF_BUILD_TESTING=OFF" "-DRF_GIT_HASH=${shortRev}" + "-G Ninja" ]; preConfigure = '' @@ -63,6 +65,7 @@ homepage = "https://github.com/3DBAG/roofer"; license = licenses.lgpl3; platforms = platforms.unix; + mainProgram = "roofer"; }; }; }); @@ -84,7 +87,7 @@ cgal gmp mpfr - pkgsStatic.boost + pkgsStatic.boost # need static for val3dity eigen fmt From f42c01b825ed14fbf5a2cdf6c6c04adb2781478f Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 5 Sep 2025 08:32:25 +0200 Subject: [PATCH 15/53] nix build: first steps integrating val3dity --- CMakeLists.txt | 1 + flake.lock | 25 +++++-- flake.nix | 121 +++++++++++++++++++++------------- rooferpy/CMakeLists.txt | 7 +- src/extra/misc/CMakeLists.txt | 20 +++--- src/extra/misc/Val3dator.cpp | 2 +- 6 files changed, 115 insertions(+), 61 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20a95a8d..0b27869f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ option(RF_BUILD_BINDINGS "Build python bindings with pybind" OFF) option(BUILD_SHARED_LIBS "Build using shared libraries (may not work)" OFF) option(RF_BUILD_TESTING "Enable tests for roofer" OFF) option(RF_ENABLE_HEAP_TRACING "Enable heap allocation overloads" OFF) +option(RF_USE_CPM "Use CPM to fetch dependencies" ON) # Enable the vcpkg features that are required by the options if(RF_USE_LOGGER_SPDLOG) diff --git a/flake.lock b/flake.lock index 96de48bd..deb500d5 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1756035328, - "narHash": "sha256-vC7SslUBCtdT3T37ZH3PLIWYmTkSeppL5BJJByUjYCM=", + "lastModified": 1756819007, + "narHash": "sha256-12V64nKG/O/guxSYnr5/nq1EfqwJCdD2+cIGmhz3nrE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6b0b1559e918d4f7d1df398ee1d33aeac586d4d6", + "rev": "aaff8c16d7fc04991cac6245bee1baa31f72b1e1", "type": "github" }, "original": { @@ -18,7 +18,24 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "val3dity-src": "val3dity-src" + } + }, + "val3dity-src": { + "flake": false, + "locked": { + "lastModified": 1757021996, + "narHash": "sha256-aX5kaHV9y9pmjEV/46dZhvFVFp/nPD/3QhEPxpnaCZc=", + "owner": "ylannl", + "repo": "val3dity", + "rev": "ebac77bc3919868c3086c9763db0389d6d4d5672", + "type": "github" + }, + "original": { + "owner": "ylannl", + "repo": "val3dity", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 68baecfd..53d65793 100644 --- a/flake.nix +++ b/flake.nix @@ -2,8 +2,10 @@ description = "Development environment for Roofer"; inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + inputs.val3dity-src.url = "github:ylannl/val3dity"; + inputs.val3dity-src.flake = false; - outputs = { self, nixpkgs, ... }: + outputs = { self, nixpkgs, val3dity-src, ... }: let supportedSystems = [ "aarch64-darwin" "x86_64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; @@ -14,60 +16,85 @@ apple_sdk = pkgs.apple-sdk_15; py = pkgs.python313; shortRev = self.shortRev or self.dirtyShortRev or "unknown"; - in { - default = pkgs.stdenv.mkDerivation { - pname = "roofer"; - version = "1.0.0-beta.5"; - - src = ./.; - nativeBuildInputs = with pkgs; [ - cmake - ninja - ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; + val3dity = pkgs.stdenv.mkDerivation { + pname = "val3dity"; + version = "2.5.3"; + src = val3dity-src; + nativeBuildInputs = with pkgs; [ cmake ninja ]; buildInputs = with pkgs; [ - # core roofer deps - cgal - gmp - mpfr + cgal gmp mpfr eigen + geos + spdlog + pugixml + tclap boost - eigen - - # app deps - mimalloc - pkgsStatic.gdalMinimal nlohmann_json - LAStools - geos - fmt - - # python binding deps - # py - # python313Packages.pybind11 - ] ++ lib.optionals stdenv.isDarwin [ apple_sdk ]; - - cmakeFlags = [ - "-DCMAKE_BUILD_TYPE=Release" - "-DRF_BUILD_APPS=ON" - # "-DRF_BUILD_BINDINGS=ON" - "-DRF_BUILD_TESTING=OFF" - "-DRF_GIT_HASH=${shortRev}" - "-G Ninja" ]; - preConfigure = '' - export pybind11_DIR="$(${py}/bin/python -c "import pybind11; print(pybind11.get_cmake_dir())")" - ''; - - meta = with pkgs.lib; { - description = "3D building reconstruction from point clouds"; - homepage = "https://github.com/3DBAG/roofer"; - license = licenses.lgpl3; - platforms = platforms.unix; - mainProgram = "roofer"; - }; + cmakeFlags = [ "-DVAL3DITY_LIBRARY=ON" "-DVAL3DITY_USE_INTERNAL_DEPS=OFF" ]; }; + + rooferDerivation = { withBindings ? false, withApps ? true }: + pkgs.stdenv.mkDerivation ({ + pname = "roofer" + pkgs.lib.optionalString withBindings "py"; + version = "1.0.0-beta.5"; + + src = ./.; + + nativeBuildInputs = with pkgs; [ + cmake + ninja + ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ] + ++ lib.optionals withBindings [ python313Packages.pybind11 ]; + + buildInputs = with pkgs; [ + # core roofer deps + cgal gmp mpfr boost eigen + fmt + ] ++ lib.optionals stdenv.isDarwin [ apple_sdk ] + ++ lib.optionals withBindings [ + py + ] + ++ lib.optionals withApps [ + val3dity + spdlog + pugixml + mimalloc + nlohmann_json + LAStools + geos + gdal + ]; + + cmakeFlags = [ + "-DCMAKE_BUILD_TYPE=Release" + "-DRF_BUILD_APPS=${if withApps then "ON" else "OFF"}" + "-DRF_BUILD_BINDINGS=${if withBindings then "ON" else "OFF"}" + "-DRF_USE_VAL3DITY=${if withApps then "ON" else "OFF"}" + "-DRF_BUILD_TESTING=OFF" + "-DRF_GIT_HASH=${shortRev}" + "-DRF_USE_CPM=OFF" + "-DRF_USE_LOGGER_SPDLOG=ON" + "-G Ninja" + ]; + + preConfigure = pkgs.lib.optionalString withBindings '' + export pybind11_DIR="$(${py}/bin/python -c "import pybind11; print(pybind11.get_cmake_dir())")" + ''; + + meta = with pkgs.lib; { + description = "3D building reconstruction from point clouds"; + homepage = "https://github.com/3DBAG/roofer"; + license = licenses.lgpl3; + platforms = platforms.unix; + mainProgram = "roofer"; + }; + }); + in { + default = rooferDerivation { withApps = true; withBindings = false; }; + python = rooferDerivation { withApps = false; withBindings = true; }; }); devShells = forAllSystems (system: diff --git a/rooferpy/CMakeLists.txt b/rooferpy/CMakeLists.txt index 8e7e91e2..f5b74901 100644 --- a/rooferpy/CMakeLists.txt +++ b/rooferpy/CMakeLists.txt @@ -13,4 +13,9 @@ target_link_libraries( PRIVATE ${RECONSTRUCT_LINK_LIBS} pybind11::module) - set_target_properties(rooferpy PROPERTIES OUTPUT_NAME "roofer") +set_target_properties(rooferpy PROPERTIES OUTPUT_NAME "roofer") + +find_package(Python REQUIRED COMPONENTS Interpreter Development) +install(TARGETS rooferpy + DESTINATION "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages" +) diff --git a/src/extra/misc/CMakeLists.txt b/src/extra/misc/CMakeLists.txt index 7b679fed..a88bfe98 100644 --- a/src/extra/misc/CMakeLists.txt +++ b/src/extra/misc/CMakeLists.txt @@ -7,7 +7,7 @@ set(LIBRARY_SOURCES "Vector2DOpsGEOS.cpp" "MeshPropertyCalculator.cpp") if(RF_USE_VAL3DITY) - list(APPEND LIBRARY_SOURCES "Val3dator.cpp") + list(APPEND LIBRARY_SOURCES "Val3dator.cpp") endif() set(LIBRARY_HEADERS "${ROOFER_INCLUDE_DIR}/roofer/misc/NodataCircleComputer.hpp" @@ -18,7 +18,7 @@ set(LIBRARY_HEADERS "${ROOFER_INCLUDE_DIR}/roofer/misc/Vector2DOps.hpp" "${ROOFER_INCLUDE_DIR}/roofer/misc/MeshPropertyCalculator.hpp") if(RF_USE_VAL3DITY) - list(APPEND LIBRARY_HEADERS "${ROOFER_INCLUDE_DIR}/roofer/misc/Val3dator.hpp") + list(APPEND LIBRARY_HEADERS "${ROOFER_INCLUDE_DIR}/roofer/misc/Val3dator.hpp") endif() set(LIBRARY_INCLUDES "${ROOFER_INCLUDE_DIR}") @@ -33,13 +33,17 @@ set(MISC_LINK_LIBS GEOS::geos_c ) if(RF_USE_VAL3DITY) + if(RF_USE_CPM) cpmaddpackage( - NAME val3dity - GITHUB_REPOSITORY "ylannl/val3dity" - GIT_TAG "master" - OPTIONS "VAL3DITY_LIBRARY ON" - "VAL3DITY_USE_INTERNAL_DEPS OFF") - list(APPEND MISC_LINK_LIBS val3dity) + NAME val3dity + GITHUB_REPOSITORY "ylannl/val3dity" + GIT_TAG "master" + OPTIONS "VAL3DITY_LIBRARY ON" + "VAL3DITY_USE_INTERNAL_DEPS OFF") + else() + find_package(val3dity REQUIRED) + endif() + list(APPEND MISC_LINK_LIBS val3dity) endif() target_link_libraries("misc" PUBLIC ${MISC_LINK_LIBS}) diff --git a/src/extra/misc/Val3dator.cpp b/src/extra/misc/Val3dator.cpp index bfc640e2..a67cbd5e 100644 --- a/src/extra/misc/Val3dator.cpp +++ b/src/extra/misc/Val3dator.cpp @@ -22,7 +22,7 @@ // val3dity #include -#include "val3dity.h" +#include namespace roofer::misc { From fa8f453dfcab5b244de55ef9c299b05680544825 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Fri, 5 Sep 2025 15:06:52 +0200 Subject: [PATCH 16/53] nix build with val3dity working --- CMakeLists.txt | 7 ++++--- flake.lock | 6 +++--- flake.nix | 11 ++++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b27869f..84093d9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,9 +80,10 @@ else() message(STATUS "Logging backend: internal") endif() -# We have to use CPM (or FetchContent) even with vcpkg, because of -# val3dity, rerun -include(CPM) +# CPM can be used to get val3dity and rerun +if(RF_USE_CPM OR RF_USE_RERUN) + include(CPM) +endif() set(GIT_IGNORE_UNTRACKED TRUE) diff --git a/flake.lock b/flake.lock index deb500d5..1fa0efad 100644 --- a/flake.lock +++ b/flake.lock @@ -25,11 +25,11 @@ "val3dity-src": { "flake": false, "locked": { - "lastModified": 1757021996, - "narHash": "sha256-aX5kaHV9y9pmjEV/46dZhvFVFp/nPD/3QhEPxpnaCZc=", + "lastModified": 1757077225, + "narHash": "sha256-KzDmiPNqfm+iWK6omyAwWPk6rm64bhB4FLcl6q2Z+Oo=", "owner": "ylannl", "repo": "val3dity", - "rev": "ebac77bc3919868c3086c9763db0389d6d4d5672", + "rev": "4ebba86b84f0096a365ed67c87533742ddfa7877", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 53d65793..f910db22 100644 --- a/flake.nix +++ b/flake.nix @@ -7,7 +7,7 @@ outputs = { self, nixpkgs, val3dity-src, ... }: let - supportedSystems = [ "aarch64-darwin" "x86_64-linux" ]; + supportedSystems = [ "x86_64-darwin" "aarch64-darwin" "x86_64-linux" "aarch64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; in { packages = forAllSystems (system: @@ -33,7 +33,7 @@ nlohmann_json ]; - cmakeFlags = [ "-DVAL3DITY_LIBRARY=ON" "-DVAL3DITY_USE_INTERNAL_DEPS=OFF" ]; + cmakeFlags = [ "-DVAL3DITY_LIBRARY=ON" "-DVAL3DITY_USE_INTERNAL_DEPS=OFF" "-G Ninja" ]; }; rooferDerivation = { withBindings ? false, withApps ? true }: @@ -60,7 +60,6 @@ ++ lib.optionals withApps [ val3dity spdlog - pugixml mimalloc nlohmann_json LAStools @@ -77,6 +76,8 @@ "-DRF_GIT_HASH=${shortRev}" "-DRF_USE_CPM=OFF" "-DRF_USE_LOGGER_SPDLOG=ON" + # there is no nix package for rerun_cpp atm + "-DRF_USE_RERUN=OFF" "-G Ninja" ]; @@ -94,7 +95,7 @@ }); in { default = rooferDerivation { withApps = true; withBindings = false; }; - python = rooferDerivation { withApps = false; withBindings = true; }; + rooferpy = rooferDerivation { withApps = false; withBindings = true; }; }); devShells = forAllSystems (system: @@ -119,11 +120,11 @@ fmt # val3dity - spdlog pugixml tclap # apps + spdlog mimalloc gdal nlohmann_json From 6c1c5461e6c8271bb0b6ed9036727f81423d5ccb Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Sat, 6 Sep 2025 10:22:00 +0200 Subject: [PATCH 17/53] add nix based dockerfile --- docker/nix.dockerfile | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docker/nix.dockerfile diff --git a/docker/nix.dockerfile b/docker/nix.dockerfile new file mode 100644 index 00000000..ebfde9e2 --- /dev/null +++ b/docker/nix.dockerfile @@ -0,0 +1,30 @@ +# from https://mitchellh.com/writing/nix-with-dockerfiles + +# Nix builder +FROM nixos/nix:latest AS builder + +# Copy our source and setup our working dir. +COPY . /tmp/build +WORKDIR /tmp/build + +# Build our Nix environment +RUN nix \ + --extra-experimental-features "nix-command flakes" \ + --option filter-syscalls false \ + build + +# Copy the Nix store closure into a directory. The Nix store closure is the +# entire set of Nix store values that we need for our build. +RUN mkdir /tmp/nix-store-closure +RUN cp -R $(nix-store -qR result/) /tmp/nix-store-closure + +# Final image is based on scratch. We copy a bunch of Nix dependencies +# but they're fully self-contained so we don't need Nix anymore. +FROM scratch + +WORKDIR /app + +# Copy /nix/store +COPY --from=builder /tmp/nix-store-closure /nix/store +COPY --from=builder /tmp/build/result /app +CMD ["/app/bin/roofer"] From 964da4ad318615f923a3faa230609b053274754a Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Sat, 6 Sep 2025 10:36:15 +0200 Subject: [PATCH 18/53] add nix based dockerfile: set entrypoint --- docker/nix.dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/nix.dockerfile b/docker/nix.dockerfile index ebfde9e2..ece127cb 100644 --- a/docker/nix.dockerfile +++ b/docker/nix.dockerfile @@ -27,4 +27,4 @@ WORKDIR /app # Copy /nix/store COPY --from=builder /tmp/nix-store-closure /nix/store COPY --from=builder /tmp/build/result /app -CMD ["/app/bin/roofer"] +ENTRYPOINT [ "/app/bin/roofer" ] From 5e4c4c9c3f2ca9cf8312ed033aad402f5a52966c Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 8 Sep 2025 21:36:46 +0200 Subject: [PATCH 19/53] flake.nix: add dockerTools output --- flake.lock | 12 ++++++------ flake.nix | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index 1fa0efad..be7041ae 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1756819007, - "narHash": "sha256-12V64nKG/O/guxSYnr5/nq1EfqwJCdD2+cIGmhz3nrE=", - "owner": "nixos", + "lastModified": 1757319647, + "narHash": "sha256-bVZyYV7fYfHqAKrlnnpOveP3jyoN++HaUy0M5aWwlv8=", + "owner": "ylannl", "repo": "nixpkgs", - "rev": "aaff8c16d7fc04991cac6245bee1baa31f72b1e1", + "rev": "bb8f29e420c5c4a7f37da1fec7dcb70d2a8a065b", "type": "github" }, "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", + "owner": "ylannl", + "ref": "gdalMinimal", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index f910db22..f19a1717 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Development environment for Roofer"; - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + inputs.nixpkgs.url = "github:ylannl/nixpkgs/gdalMinimal"; inputs.val3dity-src.url = "github:ylannl/val3dity"; inputs.val3dity-src.flake = false; @@ -98,6 +98,24 @@ rooferpy = rooferDerivation { withApps = false; withBindings = true; }; }); + dockerImage = forAllSystems (system: + let + pkgs = import nixpkgs { system = system; config.allowUnfree = true; }; + in { + roofer = pkgs.dockerTools.buildImage { + name = "roofer"; + tag = self.packages.${system}.default.version; + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = [ self.packages.${system}.default ]; + pathsToLink = [ "/bin" ]; + }; + config = { + Entrypoint = [ "roofer" ]; + }; + }; + }); + devShells = forAllSystems (system: let pkgs = import nixpkgs { system = system; config.allowUnfree = true; }; From 12c6b525d54cec5c7e7d8c2621b736f02da34138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Dukai?= Date: Mon, 15 Sep 2025 16:23:31 +0200 Subject: [PATCH 20/53] wip doc-helper --- apps/roofer-app/CMakeLists.txt | 10 +++++----- flake.nix | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index 85314a4c..1ec50b6c 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -57,11 +57,11 @@ if(RF_BUILD_DOC_HELPER) add_executable("doc-helper" "doc-helper.cpp") set_target_properties("doc-helper" PROPERTIES CXX_STANDARD 20) target_link_libraries("doc-helper" PRIVATE roofer-core fmt::fmt) - # install( - # TARGETS "doc-helper" - # ARCHIVE DESTINATION lib - # LIBRARY DESTINATION lib - # RUNTIME DESTINATION bin) + install( + TARGETS "doc-helper" + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) endif() # get the --version flag working. Try to use RF_GIT_HASH if defined (eg by nix build), otherwise fallback to cmake helper function diff --git a/flake.nix b/flake.nix index f19a1717..bd0d7ee7 100644 --- a/flake.nix +++ b/flake.nix @@ -79,6 +79,7 @@ # there is no nix package for rerun_cpp atm "-DRF_USE_RERUN=OFF" "-G Ninja" + "-DRF_BUILD_DOC_HELPER=ON" ]; preConfigure = pkgs.lib.optionalString withBindings '' From 9617c1b5ec709f08c24023f346cd1e75e350bdd7 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Mon, 15 Sep 2025 16:34:57 +0200 Subject: [PATCH 21/53] fix doc-helper build error --- apps/roofer-app/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index 1ec50b6c..c6aa1f05 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -43,6 +43,9 @@ if(RF_BUILD_APPS) ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) + if(RF_GIT_HASH) + target_compile_definitions(roofer PRIVATE RF_GIT_HASH="${RF_GIT_HASH}") + endif() if(DEFINED CMAKE_TOOLCHAIN_FILE) install (DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj DESTINATION "share" @@ -57,18 +60,19 @@ if(RF_BUILD_DOC_HELPER) add_executable("doc-helper" "doc-helper.cpp") set_target_properties("doc-helper" PROPERTIES CXX_STANDARD 20) target_link_libraries("doc-helper" PRIVATE roofer-core fmt::fmt) - install( + install( TARGETS "doc-helper" ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) + if(RF_GIT_HASH) + target_compile_definitions(doc-helper PRIVATE RF_GIT_HASH="${RF_GIT_HASH}") + endif() endif() # get the --version flag working. Try to use RF_GIT_HASH if defined (eg by nix build), otherwise fallback to cmake helper function if(RF_BUILD_DOC_HELPER OR RF_BUILD_APPS) - if(RF_GIT_HASH) - target_compile_definitions(roofer PRIVATE RF_GIT_HASH="${RF_GIT_HASH}") - else() + if(NOT RF_GIT_HASH) set(GIT_HASH_HEADER ${CMAKE_BINARY_DIR}/git_hash.h) add_custom_command( From b79b98c5f53bf1a2815bdd54fa91303f298fcf2f Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 18 Dec 2025 11:14:22 +0100 Subject: [PATCH 22/53] update flake to build with rerun --- CMakeLists.txt | 11 +++++++---- flake.lock | 18 +++++++++--------- flake.nix | 28 ++++++++++++++++++++++++---- uv.lock | 4 ---- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84093d9e..168ad42a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,10 +89,13 @@ set(GIT_IGNORE_UNTRACKED TRUE) if(RF_USE_RERUN) set(RERUN_DOWNLOAD_AND_BUILD_ARROW OFF) - cpmaddpackage( - NAME rerun_sdk URL - https://github.com/rerun-io/rerun/releases/latest/download/rerun_cpp_sdk.zip - ) + find_package(rerun_sdk QUIET) + if(NOT rerun_sdk_FOUND) + CPMAddPackage( + NAME rerun_sdk + URL https://github.com/rerun-io/rerun/releases/latest/download/rerun_cpp_sdk.zip + ) + endif() endif() if(MSVC) diff --git a/flake.lock b/flake.lock index be7041ae..5268aed5 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1757319647, - "narHash": "sha256-bVZyYV7fYfHqAKrlnnpOveP3jyoN++HaUy0M5aWwlv8=", - "owner": "ylannl", + "lastModified": 1765934234, + "narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "bb8f29e420c5c4a7f37da1fec7dcb70d2a8a065b", + "rev": "af84f9d270d404c17699522fab95bbf928a2d92f", "type": "github" }, "original": { - "owner": "ylannl", - "ref": "gdalMinimal", + "owner": "nixos", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } @@ -25,11 +25,11 @@ "val3dity-src": { "flake": false, "locked": { - "lastModified": 1757077225, - "narHash": "sha256-KzDmiPNqfm+iWK6omyAwWPk6rm64bhB4FLcl6q2Z+Oo=", + "lastModified": 1757079329, + "narHash": "sha256-ovrJ6Oqx3F8QECoWJJLHRaOczfqZ0jNV0TP99BL93M4=", "owner": "ylannl", "repo": "val3dity", - "rev": "4ebba86b84f0096a365ed67c87533742ddfa7877", + "rev": "54f7350a31ce30e0801b64bef5e9efdda1f358c3", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bd0d7ee7..25c98a34 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,8 @@ { description = "Development environment for Roofer"; - inputs.nixpkgs.url = "github:ylannl/nixpkgs/gdalMinimal"; + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + # inputs.nixpkgs.url = "github:ylannl/nixpkgs/gdalMinimal"; inputs.val3dity-src.url = "github:ylannl/val3dity"; inputs.val3dity-src.flake = false; @@ -36,7 +37,21 @@ cmakeFlags = [ "-DVAL3DITY_LIBRARY=ON" "-DVAL3DITY_USE_INTERNAL_DEPS=OFF" "-G Ninja" ]; }; - rooferDerivation = { withBindings ? false, withApps ? true }: + rerun-sdk = pkgs.stdenv.mkDerivation { + pname = "rerun-sdk"; + version = "0.27.3"; + src = pkgs.fetchurl { + url = "https://github.com/rerun-io/rerun/releases/download/0.27.3/rerun_cpp_sdk.zip"; + sha256 = "1aysn1jsl58vxaakv8j9awnnxll06ay4pwczfs8gi63w3w4yj920"; + }; + nativeBuildInputs = [ pkgs.unzip pkgs.cmake ]; + buildInputs = [ pkgs.arrow-cpp ]; + cmakeFlags = [ + "-DRERUN_DOWNLOAD_AND_BUILD_ARROW=OFF" + ]; + }; + + rooferDerivation = { withBindings ? false, withApps ? true, withRerun ? false }: pkgs.stdenv.mkDerivation ({ pname = "roofer" + pkgs.lib.optionalString withBindings "py"; version = "1.0.0-beta.5"; @@ -65,6 +80,10 @@ LAStools geos gdal + ] + ++ lib.optionals withRerun [ + rerun + rerun-sdk ]; cmakeFlags = [ @@ -75,9 +94,9 @@ "-DRF_BUILD_TESTING=OFF" "-DRF_GIT_HASH=${shortRev}" "-DRF_USE_CPM=OFF" - "-DRF_USE_LOGGER_SPDLOG=ON" + "-DRF_USE_LOGGER_SPDLOG=${if withBindings then "OFF" else "ON"}" # there is no nix package for rerun_cpp atm - "-DRF_USE_RERUN=OFF" + "-DRF_USE_RERUN=${if withRerun then "ON" else "OFF"}" "-G Ninja" "-DRF_BUILD_DOC_HELPER=ON" ]; @@ -97,6 +116,7 @@ in { default = rooferDerivation { withApps = true; withBindings = false; }; rooferpy = rooferDerivation { withApps = false; withBindings = true; }; + roofer-rerun = rooferDerivation { withApps = true; withRerun = true; }; }); dockerImage = forAllSystems (system: diff --git a/uv.lock b/uv.lock index 3073bfc2..f1068c26 100644 --- a/uv.lock +++ b/uv.lock @@ -614,11 +614,7 @@ wheels = [ [[package]] name = "roofer" -<<<<<<< HEAD version = "1.0.0b5" -======= -version = "1.0.0b3" ->>>>>>> 0c41ba1 (use official nixpkgs-unstable) source = { virtual = "." } dependencies = [ { name = "breathe" }, From a7263483ac5e6eab8a9eaf86d5d7e3d21e8ec3e2 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Tue, 24 Feb 2026 21:19:22 +0000 Subject: [PATCH 23/53] add conan build support --- apps/roofer-app/CMakeLists.txt | 6 ++- conanfile.py | 69 ++++++++++++++++++++++++++++++++++ flake.nix | 12 ++++++ src/core/logger/CMakeLists.txt | 4 +- src/extra/io/CMakeLists.txt | 2 +- src/extra/misc/CMakeLists.txt | 6 +++ 6 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 conanfile.py diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index c6aa1f05..4064fdce 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -5,7 +5,11 @@ if(RF_BUILD_APPS) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") find_package(mimalloc CONFIG REQUIRED) - list(APPEND ROOFER_LINK_LIBRARIES mimalloc) + if(TARGET mimalloc-static) + list(APPEND ROOFER_LINK_LIBRARIES mimalloc-static) + else() + list(APPEND ROOFER_LINK_LIBRARIES mimalloc) + endif() endif() add_executable("roofer" ${APP_SOURCES}) diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..619b0a0d --- /dev/null +++ b/conanfile.py @@ -0,0 +1,69 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMakeDeps + + +class RooferRecipe(ConanFile): + name = "roofer" + version = "1.0.0" + settings = "os", "compiler", "build_type", "arch" + + options = { + "build_apps": [True, False], + "use_spdlog": [True, False], + "use_val3dity": [True, False], + "build_bindings": [True, False], + "build_testing": [True, False], + } + default_options = { + "build_apps": True, + "use_spdlog": True, + "use_val3dity": False, + "build_bindings": False, + "build_testing": False, + } + + def requirements(self): + # Core deps (always required) + self.requires("cgal/6.1.1") + self.requires("eigen/3.4.0") + self.requires("fmt/11.1.3") + + if self.options.use_spdlog or self.options.use_val3dity: + self.requires("spdlog/1.15.1") + + if self.options.build_apps: + self.requires("geos/3.13.0", override=True) + self.requires("gdal/3.12.1") + self.requires("nlohmann_json/3.11.3") + self.requires("mimalloc/2.1.7") + # bshoshany-thread-pool is header-only, not yet in ConanCenter; + # leave as CPM fallback for now (RF_USE_CPM=ON) + + if self.options.use_val3dity: + self.requires("pugixml/1.15") + self.requires("tclap/1.2.5") + + if self.options.build_testing: + self.requires("catch2/3.7.1") + + def configure(self): + # Disable everything, then enable only what roofer needs + self.options["gdal"].with_arrow = False + self.options["gdal"].with_hdf4 = False + self.options["gdal"].with_hdf5 = False + self.options["gdal"].with_pg = True # postgresql + self.options["gdal"].with_sqlite3 = True + self.options["gdal"].with_geos = True + + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.variables["RF_USE_CPM"] = "OFF" + tc.variables["RF_BUILD_APPS"] = "ON" if self.options.build_apps else "OFF" + tc.variables["RF_USE_LOGGER_SPDLOG"] = "ON" if self.options.use_spdlog else "OFF" + tc.variables["RF_USE_VAL3DITY"] = "ON" if self.options.use_val3dity else "OFF" + tc.variables["RF_BUILD_BINDINGS"] = "ON" if self.options.build_bindings else "OFF" + tc.variables["RF_BUILD_TESTING"] = "ON" if self.options.build_testing else "OFF" + tc.generate() diff --git a/flake.nix b/flake.nix index 25c98a34..6f257c41 100644 --- a/flake.nix +++ b/flake.nix @@ -143,6 +143,18 @@ apple_sdk = pkgs.apple-sdk_15; py = pkgs.python313; in { + conan = pkgs.mkShell { + buildInputs = with pkgs; [ + cmake + ninja + conan + ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; + + shellHook = '' + echo "Conan dev shell ready. Run 'conan profile detect' if you haven't set up a profile yet." + ''; + }; + default = pkgs.mkShell.override { # Use stdenvNoCC to avoid compiler contamination # stdenv = if pkgs.stdenv.isDarwin then pkgs.stdenvNoCC else pkgs.stdenv; diff --git a/src/core/logger/CMakeLists.txt b/src/core/logger/CMakeLists.txt index df514201..7aadc598 100644 --- a/src/core/logger/CMakeLists.txt +++ b/src/core/logger/CMakeLists.txt @@ -6,8 +6,8 @@ add_library("logger" OBJECT ${LIBRARY_SOURCES} ${LIBRARY_HEADERS}) target_include_directories("logger" PUBLIC ${LIBRARY_INCLUDES}) if(${RF_USE_LOGGER_SPDLOG}) - target_link_libraries("logger" PRIVATE fmt::fmt spdlog::spdlog) + target_link_libraries("logger" PUBLIC fmt::fmt PRIVATE spdlog::spdlog) target_compile_definitions("logger" PRIVATE RF_USE_LOGGER_SPDLOG) else () - target_link_libraries("logger" PRIVATE fmt::fmt) + target_link_libraries("logger" PUBLIC fmt::fmt) endif() diff --git a/src/extra/io/CMakeLists.txt b/src/extra/io/CMakeLists.txt index 98a71292..49545683 100644 --- a/src/extra/io/CMakeLists.txt +++ b/src/extra/io/CMakeLists.txt @@ -26,4 +26,4 @@ find_package(GDAL CONFIG REQUIRED) add_library("io" OBJECT ${LIBRARY_SOURCES} ${LIBRARY_HEADERS}) target_include_directories("io" PUBLIC ${LIBRARY_INCLUDES}) -target_link_libraries("io" PUBLIC GDAL::GDAL LASlib nlohmann_json::nlohmann_json) +target_link_libraries("io" PUBLIC GDAL::GDAL LASlib nlohmann_json::nlohmann_json fmt::fmt) diff --git a/src/extra/misc/CMakeLists.txt b/src/extra/misc/CMakeLists.txt index a88bfe98..ee94999a 100644 --- a/src/extra/misc/CMakeLists.txt +++ b/src/extra/misc/CMakeLists.txt @@ -24,6 +24,11 @@ set(LIBRARY_INCLUDES "${ROOFER_INCLUDE_DIR}") find_package(GEOS CONFIG REQUIRED) +# Workaround: Conan's geos recipe sets an empty generator expression for +# INTERFACE_INCLUDE_DIRECTORIES on geos_c. Append the real path via find_path, +# which searches CMAKE_INCLUDE_PATH that Conan correctly populates. +find_path(GEOS_C_INCLUDE_DIR geos_c.h REQUIRED) +set_property(TARGET GEOS::geos_c APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${GEOS_C_INCLUDE_DIR}") add_library("misc" OBJECT ${LIBRARY_SOURCES} ${LIBRARY_HEADERS}) target_include_directories("misc" PUBLIC ${LIBRARY_INCLUDES}) @@ -31,6 +36,7 @@ target_include_directories("misc" PUBLIC ${LIBRARY_INCLUDES}) set(MISC_LINK_LIBS CGAL::CGAL GEOS::geos_c + fmt::fmt ) if(RF_USE_VAL3DITY) if(RF_USE_CPM) From 7936b4414386bc5adceec1be1a6f55dc8c350ed2 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 25 Feb 2026 09:01:30 +0000 Subject: [PATCH 24/53] print conan build steps + add patchelf on linux --- flake.nix | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6f257c41..6d36af68 100644 --- a/flake.nix +++ b/flake.nix @@ -148,10 +148,25 @@ cmake ninja conan - ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ]; + ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ] + ++ lib.optionals stdenv.isLinux [ patchelf ]; shellHook = '' echo "Conan dev shell ready. Run 'conan profile detect' if you haven't set up a profile yet." + echo "" + echo "Conan build steps (replace Release with Debug for debug build):" + echo " conan install . --build=missing --output-folder=build-conan -s build_type=Release" + echo " cd build-conan" + echo " cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release" + echo " cmake --build ." + echo "" + ${pkgs.lib.optionalString pkgs.stdenv.isLinux '' + roofer-patch() { + patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 "$1" + echo "Patched interpreter on $1" + } + echo "Tip: run 'roofer-patch ' to fix the ELF interpreter for deployment on regular linux systems" + ''} ''; }; From 4ea122bbbb6ffbf4619e299fc17f2e658536b4ef Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 25 Feb 2026 16:37:54 +0000 Subject: [PATCH 25/53] undo mimalloc static to keep nix build going --- apps/roofer-app/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index 4064fdce..df0e9da0 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -5,11 +5,11 @@ if(RF_BUILD_APPS) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") find_package(mimalloc CONFIG REQUIRED) - if(TARGET mimalloc-static) - list(APPEND ROOFER_LINK_LIBRARIES mimalloc-static) - else() + # if(TARGET mimalloc-static) + # list(APPEND ROOFER_LINK_LIBRARIES mimalloc-static) + # else() list(APPEND ROOFER_LINK_LIBRARIES mimalloc) - endif() + # endif() endif() add_executable("roofer" ${APP_SOURCES}) From 5226b65b2b122acbdd41bd7bd2455200c26592e1 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 11 Mar 2026 23:03:20 +0000 Subject: [PATCH 26/53] conan based build action --- .github/workflows/build_install_conan.yml | 89 +++++++++++++++++++++++ .gitignore | 2 +- apps/roofer-app/CMakeLists.txt | 20 +++-- conanfile.py | 19 ++++- 4 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/build_install_conan.yml diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml new file mode 100644 index 00000000..4761b816 --- /dev/null +++ b/.github/workflows/build_install_conan.yml @@ -0,0 +1,89 @@ +name: Build and Install (Conan) + +on: + push: + tags: + - "v*" + pull_request: + branches: ["develop"] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, windows-2022, macos-15] + build_type: [Release] + + steps: + - uses: actions/checkout@v4 + + - name: Install and setup Conan + uses: conan-io/setup-conan@v1 + with: + cache_packages: true + use_venv: true + + - name: Detect Conan profile + run: conan profile detect --force + + - name: Install dependencies + run: > + conan install . + --output-folder=build + --build=missing + --settings=build_type=${{ matrix.build_type }} + --options="&:build_apps=True" + --options="&:use_spdlog=True" + --options="&:use_val3dity=False" + --options="&:build_bindings=False" + --options="&:build_testing=False" + + - name: Configure CMake + run: > + cmake -B build + -S ${{ github.workspace }} + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" + -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install" + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + + - name: Build + run: cmake --build build --config ${{ matrix.build_type }} --verbose + + - name: Install + run: cmake --install build --config ${{ matrix.build_type }} --verbose + + - if: runner.os == 'Windows' + name: Show install tree + run: | + cd install + tree /F + + - if: runner.os != 'Windows' + name: Show install tree + run: | + cd install + ls -R + + - if: runner.os == 'macOS' + name: Bundle macOS runtime libraries + run: | + cd install/bin + bash "${{ github.workspace }}/distribution/macOS/bundle_libcxx.sh" + + - name: Set lowercase runner variables + shell: bash + run: | + echo "RUNNER_OS_LC=$(echo '${{ runner.os }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "RUNNER_ARCH_LC=$(echo '${{ runner.arch }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Upload binaries + uses: actions/upload-artifact@v4 + with: + name: roofer-conan-${{ env.RUNNER_OS_LC }}-${{ env.RUNNER_ARCH_LC }}-${{ github.sha }} + path: | + install + retention-days: 7 diff --git a/.gitignore b/.gitignore index 18db9775..b1116ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Build dirs cmake-build-* vcpkg_installed -build* +./build* # CMake CMakeUserPresets.json diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index d23448dc..d621d309 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -41,13 +41,19 @@ if(RF_BUILD_APPS) if(RF_GIT_HASH) target_compile_definitions(roofer PRIVATE RF_GIT_HASH="${RF_GIT_HASH}") endif() - if(DEFINED CMAKE_TOOLCHAIN_FILE) - install (DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj - DESTINATION "share" - FILES_MATCHING PATTERN "*" PATTERN "*.cmake" EXCLUDE) - install(DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/gdal - DESTINATION "share" - FILES_MATCHING PATTERN "*" PATTERN "*.cmake" EXCLUDE) + if(DEFINED VCPKG_TARGET_TRIPLET) + if(EXISTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj") + install( + DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj + DESTINATION "share" + FILES_MATCHING PATTERN "*" PATTERN "*.cmake" EXCLUDE) + endif() + if(EXISTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/gdal") + install( + DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/gdal + DESTINATION "share" + FILES_MATCHING PATTERN "*" PATTERN "*.cmake" EXCLUDE) + endif() endif() endif() diff --git a/conanfile.py b/conanfile.py index 619b0a0d..cc31caa2 100644 --- a/conanfile.py +++ b/conanfile.py @@ -47,13 +47,28 @@ def requirements(self): self.requires("catch2/3.7.1") def configure(self): - # Disable everything, then enable only what roofer needs + # Keep GDAL close to the minimum feature set Roofer uses: + # GeoPackage, PostgreSQL/PostGIS, and GeoTIFF. self.options["gdal"].with_arrow = False + self.options["gdal"].with_curl = False + self.options["gdal"].with_expat = False + self.options["gdal"].with_geos = True + self.options["gdal"].with_gif = False self.options["gdal"].with_hdf4 = False self.options["gdal"].with_hdf5 = False + self.options["gdal"].with_jpeg = False + self.options["gdal"].with_lerc = False + self.options["gdal"].with_libdeflate = False + self.options["gdal"].with_opencl = False self.options["gdal"].with_pg = True # postgresql + self.options["gdal"].with_png = False + self.options["gdal"].with_qhull = False self.options["gdal"].with_sqlite3 = True - self.options["gdal"].with_geos = True + self.options["libtiff"].jpeg = False + self.options["gdal"].gdal_optional_drivers = False + # Keep OGR optional drivers enabled: the PG driver depends on PGDump, + # and ConanCenter does not expose per-driver toggles here. + self.options["gdal"].ogr_optional_drivers = True def generate(self): From 478eff3f9a41148ec92a732be3a50b20f81c6994 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Wed, 11 Mar 2026 23:14:30 +0000 Subject: [PATCH 27/53] set conan profile cppstd=20 --- .github/workflows/build_install_conan.yml | 1 + .github/workflows/build_install_test_vcpkg.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index 4761b816..a7ea9b52 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -36,6 +36,7 @@ jobs: --output-folder=build --build=missing --settings=build_type=${{ matrix.build_type }} + --settings=compiler.cppstd=20 --options="&:build_apps=True" --options="&:use_spdlog=True" --options="&:use_val3dity=False" diff --git a/.github/workflows/build_install_test_vcpkg.yml b/.github/workflows/build_install_test_vcpkg.yml index 5002dbcb..440a8a2a 100644 --- a/.github/workflows/build_install_test_vcpkg.yml +++ b/.github/workflows/build_install_test_vcpkg.yml @@ -7,8 +7,8 @@ on: push: tags: - "v*" - pull_request: - branches: ["develop"] + # pull_request: + # branches: ["develop"] workflow_dispatch: From b4a75264e38f337a6359f665d35295556e2f5496 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 07:52:24 +0000 Subject: [PATCH 28/53] conan: include laslib --- conanfile.py | 2 +- src/extra/io/CMakeLists.txt | 10 +++++++++- src/extra/io/PointCloudReaderLASlib.cpp | 16 +++++++++++++++- src/extra/io/PointCloudWriterLASlib.cpp | 9 ++++++++- src/extra/io/StreamCropper.cpp | 10 +++++++++- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/conanfile.py b/conanfile.py index cc31caa2..f9b841e7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -35,7 +35,7 @@ def requirements(self): self.requires("geos/3.13.0", override=True) self.requires("gdal/3.12.1") self.requires("nlohmann_json/3.11.3") - self.requires("mimalloc/2.1.7") + self.requires("laslib/2.0.2") # bshoshany-thread-pool is header-only, not yet in ConanCenter; # leave as CPM fallback for now (RF_USE_CPM=ON) diff --git a/src/extra/io/CMakeLists.txt b/src/extra/io/CMakeLists.txt index 49545683..081c556f 100644 --- a/src/extra/io/CMakeLists.txt +++ b/src/extra/io/CMakeLists.txt @@ -24,6 +24,14 @@ find_package(nlohmann_json CONFIG REQUIRED) # GDAL find_package(GDAL CONFIG REQUIRED) +if(TARGET laslib::laslib) + set(LASLIB_TARGET laslib::laslib) +elseif(TARGET LASlib) + set(LASLIB_TARGET LASlib) +else() + message(FATAL_ERROR "Could not find a usable LASlib target") +endif() + add_library("io" OBJECT ${LIBRARY_SOURCES} ${LIBRARY_HEADERS}) target_include_directories("io" PUBLIC ${LIBRARY_INCLUDES}) -target_link_libraries("io" PUBLIC GDAL::GDAL LASlib nlohmann_json::nlohmann_json fmt::fmt) +target_link_libraries("io" PUBLIC GDAL::GDAL ${LASLIB_TARGET} nlohmann_json::nlohmann_json fmt::fmt) diff --git a/src/extra/io/PointCloudReaderLASlib.cpp b/src/extra/io/PointCloudReaderLASlib.cpp index f7d389be..eaad9e11 100644 --- a/src/extra/io/PointCloudReaderLASlib.cpp +++ b/src/extra/io/PointCloudReaderLASlib.cpp @@ -23,9 +23,23 @@ #include #include +#include + +#if __has_include() +#include +#elif __has_include() #include +#else +#error "LASlib header lasreader.hpp not found" +#endif + +#if __has_include() +#include +#elif __has_include() #include -#include +#else +#error "LASlib header laswriter.hpp not found" +#endif namespace roofer::io { diff --git a/src/extra/io/PointCloudWriterLASlib.cpp b/src/extra/io/PointCloudWriterLASlib.cpp index 62c49f91..ff16c22e 100644 --- a/src/extra/io/PointCloudWriterLASlib.cpp +++ b/src/extra/io/PointCloudWriterLASlib.cpp @@ -23,9 +23,16 @@ #include #include -#include #include +#if __has_include() +#include +#elif __has_include() +#include +#else +#error "LASlib header laswriter.hpp not found" +#endif + namespace roofer::io { namespace fs = std::filesystem; diff --git a/src/extra/io/StreamCropper.cpp b/src/extra/io/StreamCropper.cpp index bf059b5a..bfe978a2 100644 --- a/src/extra/io/StreamCropper.cpp +++ b/src/extra/io/StreamCropper.cpp @@ -25,11 +25,19 @@ #include #include #include -#include + #include #include #include +#if __has_include() +#include +#elif __has_include() +#include +#else +#error "LASlib header lasreader.hpp not found" +#endif + namespace roofer::io { namespace fs = std::filesystem; From 65a15fa8119e1564c4834c361935ef26df55a157 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 07:55:37 +0000 Subject: [PATCH 29/53] disable docker ci --- .github/workflows/build-push-docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index 8ed1053d..91a69920 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -9,9 +9,9 @@ on: - "**.rst" branches: - develop - pull_request: - branches: - - develop + # pull_request: + # branches: + # - develop jobs: build: From afa4b3cf85ab0c1815709f7b63e0750c398e23ff Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 08:39:16 +0000 Subject: [PATCH 30/53] bundle gdal/proj data, remove macos bundle step --- .github/workflows/build_install_conan.yml | 6 -- .../workflows/build_install_test_vcpkg.yml | 6 -- conanfile.py | 1 + distribution/macOS/bundle_libcxx.sh | 70 ------------------- flake.nix | 5 -- src/extra/io/CMakeLists.txt | 13 ++++ 6 files changed, 14 insertions(+), 87 deletions(-) delete mode 100755 distribution/macOS/bundle_libcxx.sh diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index a7ea9b52..d0eacc4c 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -69,12 +69,6 @@ jobs: cd install ls -R - - if: runner.os == 'macOS' - name: Bundle macOS runtime libraries - run: | - cd install/bin - bash "${{ github.workspace }}/distribution/macOS/bundle_libcxx.sh" - - name: Set lowercase runner variables shell: bash run: | diff --git a/.github/workflows/build_install_test_vcpkg.yml b/.github/workflows/build_install_test_vcpkg.yml index 440a8a2a..ea7525aa 100644 --- a/.github/workflows/build_install_test_vcpkg.yml +++ b/.github/workflows/build_install_test_vcpkg.yml @@ -202,12 +202,6 @@ jobs: run: | cd install ls -R - - if: runner.os == 'macOS' - name: show contents install dir - run: | - cd install/bin - bundle_libcxx.sh roofer - - name: Test installed shell: bash run: ctest --preset test-installed --build-config ${{ matrix.build_type }} --test-dir build --output-on-failure diff --git a/conanfile.py b/conanfile.py index f9b841e7..34d6c1d3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -69,6 +69,7 @@ def configure(self): # Keep OGR optional drivers enabled: the PG driver depends on PGDump, # and ConanCenter does not expose per-driver toggles here. self.options["gdal"].ogr_optional_drivers = True + self.options["gdal"].tools = False def generate(self): diff --git a/distribution/macOS/bundle_libcxx.sh b/distribution/macOS/bundle_libcxx.sh deleted file mode 100755 index 3bec91ef..00000000 --- a/distribution/macOS/bundle_libcxx.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -# Script to bundle dynamic libraries for roofer binary into ../lib and update paths - -set -e - -# Define paths -BINARY="roofer" -LIB_DIR="$(dirname "$BINARY")/../lib" -mkdir -p "$LIB_DIR" - -# Function to check if a library is a system library -is_system_lib() { - local lib_path="$1" - if [[ "$lib_path" == /usr/lib/* || "$lib_path" == /System/Library/* ]]; then - return 0 # True (is system library) - else - return 1 # False (not system library) - fi -} - -# Function to get dependencies of a binary or library -get_deps() { - local file="$1" - otool -L "$file" | tail -n +2 | awk '{print $1}' | while read -r dep; do - echo "$dep" - done -} - -# Copy non-system libraries to LIB_DIR and update paths -copy_and_update_lib() { - local lib_path="$1" - local target_file="$2" # Binary or library to update - local lib_name=$(basename "$lib_path") - local new_path="@executable_path/../lib/$lib_name" - - if ! is_system_lib "$lib_path"; then - # Copy library to LIB_DIR - cp "$lib_path" "$LIB_DIR/$lib_name" - chmod +w "$LIB_DIR/$lib_name" - - # Update the library's own ID - install_name_tool -id "$new_path" "$LIB_DIR/$lib_name" - - # Update the path in the target file - install_name_tool -change "$lib_path" "$new_path" "$target_file" - - # Process dependencies of the copied library - for dep in $(get_deps "$lib_path"); do - local dep_name=$(basename "$dep") - local new_dep_path="@executable_path/../lib/$dep_name" - if ! is_system_lib "$dep"; then - cp "$dep" "$LIB_DIR/$dep_name" - chmod +w "$LIB_DIR/$dep_name" - install_name_tool -id "$new_dep_path" "$LIB_DIR/$dep_name" - install_name_tool -change "$dep" "$new_dep_path" "$LIB_DIR/$lib_name" - fi - done - fi -} - -# Ensure the binary is writable -chmod +w "$BINARY" - -# Process dependencies of the roofer binary -for dep in $(get_deps "$BINARY"); do - copy_and_update_lib "$dep" "$BINARY" -done - -echo "Bundling complete. Libraries copied to $LIB_DIR and paths updated." diff --git a/flake.nix b/flake.nix index 6d36af68..3e111921 100644 --- a/flake.nix +++ b/flake.nix @@ -210,11 +210,6 @@ scm_version = "unknown"; shellHook = '' - ${pkgs.lib.optionalString pkgs.stdenv.isDarwin '' - # distribution script for macOS - chmod +x distribution/macOS/bundle_libcxx.sh - export PATH="$(pwd)/distribution/macOS:$PATH" - ''} echo "Updating and activating python environment..." uv sync source .venv/bin/activate diff --git a/src/extra/io/CMakeLists.txt b/src/extra/io/CMakeLists.txt index 081c556f..ffd64e22 100644 --- a/src/extra/io/CMakeLists.txt +++ b/src/extra/io/CMakeLists.txt @@ -21,6 +21,7 @@ set(LIBRARY_INCLUDES find_package(laslib CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) +find_package(proj CONFIG REQUIRED) # GDAL find_package(GDAL CONFIG REQUIRED) @@ -35,3 +36,15 @@ endif() add_library("io" OBJECT ${LIBRARY_SOURCES} ${LIBRARY_HEADERS}) target_include_directories("io" PUBLIC ${LIBRARY_INCLUDES}) target_link_libraries("io" PUBLIC GDAL::GDAL ${LASLIB_TARGET} nlohmann_json::nlohmann_json fmt::fmt) + +if(DEFINED gdal_PACKAGE_FOLDER_RELEASE AND EXISTS "${gdal_PACKAGE_FOLDER_RELEASE}/res/gdal") + install( + DIRECTORY "${gdal_PACKAGE_FOLDER_RELEASE}/res/gdal" + DESTINATION "share") +endif() + +if(DEFINED proj_PACKAGE_FOLDER_RELEASE AND EXISTS "${proj_PACKAGE_FOLDER_RELEASE}/res") + install( + DIRECTORY "${proj_PACKAGE_FOLDER_RELEASE}/res/" + DESTINATION "share/proj") +endif() From d0f18e583ed564fdcf4e583b7dec17ec636d33c2 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 09:02:50 +0000 Subject: [PATCH 31/53] update actions versions --- .github/workflows/build-push-docker.yml | 2 +- .github/workflows/build_install_conan.yml | 4 ++-- .github/workflows/build_install_test_vcpkg.yml | 6 +++--- .github/workflows/cpp-linter.yml | 10 +++++----- .github/workflows/documentation.yml | 6 +++--- .github/workflows/pre-commit.yml | 14 +++++++------- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index 91a69920..b903e976 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -17,7 +17,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Set docker image tag to 'develop' run: echo "DOCKER_TAG=develop" >> $GITHUB_ENV - name: Set docker image tag to git tag diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index d0eacc4c..66165790 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -19,7 +19,7 @@ jobs: build_type: [Release] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install and setup Conan uses: conan-io/setup-conan@v1 @@ -76,7 +76,7 @@ jobs: echo "RUNNER_ARCH_LC=$(echo '${{ runner.arch }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - name: Upload binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: roofer-conan-${{ env.RUNNER_OS_LC }}-${{ env.RUNNER_ARCH_LC }}-${{ github.sha }} path: | diff --git a/.github/workflows/build_install_test_vcpkg.yml b/.github/workflows/build_install_test_vcpkg.yml index ea7525aa..d04ca9d7 100644 --- a/.github/workflows/build_install_test_vcpkg.yml +++ b/.github/workflows/build_install_test_vcpkg.yml @@ -61,7 +61,7 @@ jobs: VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/3DBAG/index.json,readwrite" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/github-script@v7 with: script: | @@ -130,7 +130,7 @@ jobs: - if: runner.os != 'Windows' name: Restore uv cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /tmp/.uv-cache key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} @@ -218,7 +218,7 @@ jobs: - name: Upload binaries if: startsWith(github.ref, 'refs/tags/') - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: roofer-${{ env.RUNNER_OS_LC }}-${{ env.RUNNER_ARCH_LC }}-${{ github.ref_name }} path: | diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml index 5e96619d..26389a5c 100644 --- a/.github/workflows/cpp-linter.yml +++ b/.github/workflows/cpp-linter.yml @@ -2,8 +2,8 @@ name: cpp-linter on: pull_request: - branches: [ develop ] - paths: ['**.cpp', '**.h', '**.hpp'] + branches: [develop] + paths: ["**.cpp", "**.h", "**.hpp"] jobs: cpp-linter: @@ -13,15 +13,15 @@ jobs: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: cpp-linter/cpp-linter-action@main id: linter continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - style: 'file' # Use .clang-format config file - tidy-checks: '' # Use .clang-tidy config file + style: "file" # Use .clang-format config file + tidy-checks: "" # Use .clang-tidy config file files-changed-only: false thread-comments: false diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a8b8a717..f9bd006d 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -3,7 +3,7 @@ name: Documentation on: push: branches: - - 'main' + - "main" # pull_request: # branches: ["develop"] @@ -21,7 +21,7 @@ jobs: VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/3DBAG/index.json,readwrite" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/github-script@v7 with: script: | @@ -35,7 +35,7 @@ jobs: name: "install nix" - name: Restore uv cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /tmp/.uv-cache key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 372f3101..193f64ac 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -2,15 +2,15 @@ name: Formatting on: pull_request: - branches: [ "develop" ] + branches: ["develop"] jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - cache: 'pip' - - uses: pre-commit/action@v3.0.1 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + - uses: pre-commit/action@v3.0.1 From aa68d3317bd0b43dc7fa3bfa4aa70ecda1b31bdc Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:11:47 +0000 Subject: [PATCH 32/53] conan build ci: add testing --- .github/workflows/build_install_conan.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index 66165790..87a8bd35 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -41,7 +41,7 @@ jobs: --options="&:use_spdlog=True" --options="&:use_val3dity=False" --options="&:build_bindings=False" - --options="&:build_testing=False" + --options="&:build_testing=True" - name: Configure CMake run: > @@ -57,6 +57,12 @@ jobs: - name: Install run: cmake --install build --config ${{ matrix.build_type }} --verbose + - name: Test + env: + GDAL_DATA: ${{ github.workspace }}/install/share/gdal + PROJ_DATA: ${{ github.workspace }}/install/share/proj + run: ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure + - if: runner.os == 'Windows' name: Show install tree run: | From 7430e802abbde39e358a9a962ecbdd4fdc58df18 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:29:26 +0000 Subject: [PATCH 33/53] update dockerfile to use conan --- .../CMakeFiles/4.1.2/CompilerIdCXX/a.out | Bin 0 -> 33736 bytes docker/Dockerfile | 108 ++++++++---------- 2 files changed, 45 insertions(+), 63 deletions(-) create mode 100755 build-conan/CMakeFiles/4.1.2/CompilerIdCXX/a.out diff --git a/build-conan/CMakeFiles/4.1.2/CompilerIdCXX/a.out b/build-conan/CMakeFiles/4.1.2/CompilerIdCXX/a.out new file mode 100755 index 0000000000000000000000000000000000000000..a166c1ca3e2a20086b85833a90d5b25a0f76cdd9 GIT binary patch literal 33736 zcmeI*e{7RQ7zgmDKPDX@RALB=1FL_`O=NT%QDP=`jtK+8ma)V_GtQT_8x37cx>gxK zw8NmNF&V}mXfQ58<4k6P2BR?;&=`Zpgz$$fafw1A31C1I6OG`&=ehUZuI*3~jsJZw zx!yhZ+7`{GpBlew z{jUwfo9cNiz*Y7@#H*Ce)_r}u{!cu2^QgW*ohM@7oU9|?%fce>8{{fE{e3u`;M;Lc z?uGZ$y>MY|T`g}azDe(MY2kcY!9SDY`RfMyGqJwfhs-5Bo2ZJb-p`PbhQ1C;jd7?0#CL=lQAGv~8x1o9p##yg&c~ z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1R(Ig2?TiMc2ng|3jg-oD+8v;f0H{! z_d_=kg`AP%>&T_3T>p`|NA+!1v2C&LY$m2Q$E;{9O=a<9PfAfkpA}Ei0hgDCa{Q)V zd58inavp?05qauD*6NKZ>UEyFK(FxZ9MAUBuw3Jw`bY(`N*};LLwwPGebHWi_0Z@p zXV*^e86KF$``yFOJCOUAt8=b1QdVxNfz zCFTp|Sq*Z1fYf!Ily=fAb~kThhk2muD3r{YVq)j>u1bFvN^MwESFhjIJcfezR#+~7 zKl2#x_(dL*ZA-b8$C%6Y1i2Z=t?4%McvxqN%87Od18)77^=9awnvj!pS+%KbhOYfQ zex&N+M~!Dkx&mY4>)-b^UO6$~J2-IZ-iF$$$Qv8qdvf%HaO%tRJ;%IbCr@s1w}s2D z?L545`=0MUeSYP$_q=nX{E}C@@4V%U@Zjf%PJVsl^$Wib_0He+L$&L*o&{IWygd5W zoS)W>9_Tpq;fVjY-WSeRZW);NbaSOU`@oN<8W+4BUzI&~qxJu5{^;?22X@Up*R<>4 fnLk^%52dF)`pboiD`Tf`zVzJcZ%#k*ht2gD6fMp= literal 0 HcmV?d00001 diff --git a/docker/Dockerfile b/docker/Dockerfile index e71832d2..9c52a5b5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,95 +1,77 @@ -FROM ubuntu:24.04 AS vcpkg-build - -# Set the location for the vcpkg download cache -RUN mkdir -p /var/cache/vcpkg -ENV VCPKG_ROOT=/opt/vcpkg -ENV VCPKG_DOWNLOADS=/var/cache/vcpkg -ENV VCPKG_DEFAULT_TRIPLET=x64-linux -ENV VCPKG_BUILD_TYPE=release - -# Install dependencies for building vcpkg and packages -RUN apt-get update \ - && apt-get install -y g++ gcc cmake git curl zip unzip tar openssl ca-certificates \ - pkg-config autoconf-archive autoconf libtool autotools-dev automake - -# Clone vcpkg -WORKDIR /opt -RUN git clone https://github.com/microsoft/vcpkg.git - -# Bootstrap vcpkg -WORKDIR /opt/vcpkg -RUN ./bootstrap-vcpkg.sh - - FROM ubuntu:24.04 AS builder + ARG JOBS=2 +ARG VERSION=unknown ARG ROOFER_ROOT=/opt/roofer ARG ROOFER_INSTALL=$ROOFER_ROOT/install ARG ROOFER_BUILD=$ROOFER_ROOT/build -# Set the VCPKG_ROOT environment variable so your build system can find it -ENV VCPKG_ROOT=/opt/vcpkg -# Cache the downloads from vcpkg in the builder stage as well -ENV VCPKG_DOWNLOADS=/var/cache/vcpkg -ENV VCPKG_DEFAULT_TRIPLET=x64-linux -ENV VCPKG_BUILD_TYPE=release + +ENV DEBIAN_FRONTEND=noninteractive +ENV PATH=/opt/conan-venv/bin:$PATH RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ --mount=target=/var/cache/apt,type=cache,sharing=locked \ rm -f /etc/apt/apt.conf.d/docker-clean \ && apt-get update \ - && apt-get -y install g++ gcc cmake git curl zip unzip tar openssl ca-certificates \ - pkg-config ninja-build autoconf-archive autoconf libtool bison flex autotools-dev automake linux-libc-dev \ - python3 bison flex \ + && apt-get -y install \ + ca-certificates \ + cmake \ + curl \ + g++ \ + gcc \ + git \ + ninja-build \ + python3 \ + python3-venv \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Copy vcpkg artifacts from the previous stage -COPY --from=vcpkg-build /opt/vcpkg /opt/vcpkg -COPY --from=vcpkg-build /var/cache/vcpkg /var/cache/vcpkg +RUN python3 -m venv /opt/conan-venv \ + && /opt/conan-venv/bin/pip install --upgrade pip \ + && /opt/conan-venv/bin/pip install "conan>=2,<3" -RUN mkdir -p $ROOFER_ROOT/install $ROOFER_ROOT/build/vcpkg_installed +RUN mkdir -p $ROOFER_ROOT/install $ROOFER_ROOT/build WORKDIR $ROOFER_ROOT -# We only need .git because of the cmake-git-version-tracking -COPY --link ./.git $ROOFER_ROOT/.git + COPY --link ./apps $ROOFER_ROOT/apps COPY --link ./cmake $ROOFER_ROOT/cmake COPY --link ./include $ROOFER_ROOT/include -COPY --link ./rooferpy $ROOFER_ROOT/rooferpy COPY --link ./src $ROOFER_ROOT/src COPY --link ./CMakeLists.txt $ROOFER_ROOT/CMakeLists.txt -COPY --link ./CMakePresets.json $ROOFER_ROOT/CMakePresets.json COPY --link ./LICENSE $ROOFER_ROOT/LICENSE COPY --link ./README.md $ROOFER_ROOT/README.md -# COPY --link ./requirements.txt $ROOFER_ROOT/requirements.txt -COPY --link ./vcpkg.json $ROOFER_ROOT/vcpkg.json - -WORKDIR $ROOFER_ROOT -RUN $VCPKG_ROOT/vcpkg x-update-baseline - -RUN --mount=target=$ROOFER_BUILD/vcpkg_installed,type=cache cmake \ - --preset vcpkg-minimal \ - -DRF_USE_LOGGER_SPDLOG=ON \ - -DRF_BUILD_APPS=ON \ - -DRF_USE_VAL3DITY=ON \ - -DCMAKE_INSTALL_PREFIX=$ROOFER_INSTALL \ - -S $ROOFER_ROOT \ - -B $ROOFER_BUILD \ - -G Ninja \ - && cmake --build $ROOFER_BUILD -j $JOBS --target install --config Release \ - && cp -r $ROOFER_BUILD/vcpkg_installed/x64-linux/share/gdal $ROOFER_INSTALL/gdal \ - && cp -r $ROOFER_BUILD/vcpkg_installed/x64-linux/share/proj $ROOFER_INSTALL/proj +COPY --link ./conanfile.py $ROOFER_ROOT/conanfile.py + +RUN --mount=type=cache,target=/root/.conan2 \ + conan profile detect --force \ + && conan install . \ + --output-folder=$ROOFER_BUILD \ + --build=missing \ + --settings=build_type=Release \ + --settings=compiler.cppstd=20 \ + --options="&:build_apps=True" \ + --options="&:use_spdlog=True" \ + --options="&:use_val3dity=False" \ + --options="&:build_bindings=False" \ + --options="&:build_testing=False" \ + && cmake -B $ROOFER_BUILD \ + -S $ROOFER_ROOT \ + -DCMAKE_TOOLCHAIN_FILE=$ROOFER_BUILD/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$ROOFER_INSTALL \ + -DRF_GIT_HASH=$VERSION \ + && cmake --build $ROOFER_BUILD -j $JOBS --target install --config Release FROM ubuntu:24.04 AS production + ARG ROOFER_ROOT=/opt/roofer -ARG ROOFER_INSTALL=$ROOFER_ROOT/install -ARG ROOFER_BUILD=$ROOFER_ROOT/build -COPY --link --from=builder $ROOFER_INSTALL/bin /opt/roofer/bin -COPY --link --from=builder $ROOFER_INSTALL/gdal /opt/roofer/share/gdal -COPY --link --from=builder $ROOFER_INSTALL/proj /opt/roofer/share/proj +COPY --link --from=builder $ROOFER_ROOT/install/ /opt/roofer/ ENV GDAL_DATA=/opt/roofer/share/gdal ENV PROJ_DATA=/opt/roofer/share/proj ENV PATH=/opt/roofer/bin:$PATH + +ENTRYPOINT ["roofer"] From 9d1ed1d9794a07db69a9a59a7b28e7c15c8c2cca Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:29:42 +0000 Subject: [PATCH 34/53] conan build ci: enable testing --- .github/workflows/build_install_conan.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index 87a8bd35..a3ee6028 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -50,6 +50,12 @@ jobs: -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install" -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DRF_BUILD_APPS=ON + -DRF_USE_LOGGER_SPDLOG=ON + -DRF_USE_VAL3DITY=OFF + -DRF_BUILD_BINDINGS=OFF + -DRF_BUILD_TESTING=ON + -DRF_USE_CPM=OFF - name: Build run: cmake --build build --config ${{ matrix.build_type }} --verbose From de0ceabb73d9f5900ce06a9d68f5e2985a0607e8 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:37:54 +0000 Subject: [PATCH 35/53] fix test, enable docker ci --- .github/workflows/build-push-docker.yml | 6 +++--- tests/test_reconstruct_api.cpp | 9 +-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index b903e976..9db34b6e 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -9,9 +9,9 @@ on: - "**.rst" branches: - develop - # pull_request: - # branches: - # - develop + pull_request: + branches: + - develop jobs: build: diff --git a/tests/test_reconstruct_api.cpp b/tests/test_reconstruct_api.cpp index b667f606..db2ac7e2 100644 --- a/tests/test_reconstruct_api.cpp +++ b/tests/test_reconstruct_api.cpp @@ -29,7 +29,6 @@ #include "argh.h" #include "fmt/format.h" -#include "git.h" typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef CGAL::Point_3 Point_3; @@ -44,13 +43,7 @@ void print_help(std::string program_name) { std::cout << " -v, --verbose Be more verbose" << "\n"; } // ... get the input pointcloud and footprint polygon for your building -void print_version() { - std::cout << fmt::format( - "roofer {} ({}{}{})\n", git_Describe(), - std::strcmp(git_Branch(), "main") ? "" - : fmt::format("{}, ", git_Branch()), - git_AnyUncommittedChanges() ? "dirty, " : "", git_CommitDate()); -} +void print_version() { std::cout << "roofer test_reconstruct_api\n"; } int main(int argc, const char* argv[]) { auto cmdl = argh::parser(); From c060269a1e34f66e79a8c7d2d7bb57c18d42712b Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:46:32 +0000 Subject: [PATCH 36/53] conan co: enable test_install --- .github/workflows/build_install_conan.yml | 1 + .../workflows/build_install_test_vcpkg.yml | 226 ------------------ 2 files changed, 1 insertion(+), 226 deletions(-) delete mode 100644 .github/workflows/build_install_test_vcpkg.yml diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index a3ee6028..0e0921b5 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -55,6 +55,7 @@ jobs: -DRF_USE_VAL3DITY=OFF -DRF_BUILD_BINDINGS=OFF -DRF_BUILD_TESTING=ON + -DRF_TEST_INSTALL=ON -DRF_USE_CPM=OFF - name: Build diff --git a/.github/workflows/build_install_test_vcpkg.yml b/.github/workflows/build_install_test_vcpkg.yml deleted file mode 100644 index d04ca9d7..00000000 --- a/.github/workflows/build_install_test_vcpkg.yml +++ /dev/null @@ -1,226 +0,0 @@ -# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml -name: Build and Test (vcpkg) - -on: - # push to main branch - push: - tags: - - "v*" - # pull_request: - # branches: ["develop"] - - workflow_dispatch: - -jobs: - build: - runs-on: ${{ matrix.os }} - - strategy: - # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. - fail-fast: false - - # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. - # See: https://github.com/actions/runner-images?tab=readme-ov-file#available-images - matrix: - os: [ubuntu-24.04, windows-2022, macos-15] - build_type: [Release] - # c_compiler: [gcc, clang, cl] - # Default compilers: - # - Ubuntu 22.04 (jammy): gcc-11, clang-14 - # - Ubuntu 24.04 (noble): gcc-13, clang-18 - # - macOS 14: clang-15 - # include: - # - os: ubuntu-24.04 - # c_compiler: gcc - # cpp_compiler: g++ - # - os: ubuntu-24.04 - # c_compiler: clang - # cpp_compiler: clang++ - # - os: windows-2022 - # c_compiler: cl - # cpp_compiler: cl - # - os: macos-15 - # c_compiler: clang - # cpp_compiler: clang++ - # exclude: - # - os: ubuntu-24.04 - # c_compiler: cl - # - os: windows-2022 - # c_compiler: gcc - # - os: windows-2022 - # c_compiler: clang - # - os: macos-15 - # c_compiler: cl - # - os: macos-15 - # c_compiler: gcc - - env: - USERNAME: 3DBAG - FEED_URL: https://nuget.pkg.github.com/3DBAG/index.json - VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/3DBAG/index.json,readwrite" - - steps: - - uses: actions/checkout@v5 - - uses: actions/github-script@v7 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - # Need python for running the tests on the installed exes - - uses: actions/setup-python@v5 - if: runner.os == 'Windows' - with: - python-version: "3.13" - - name: Install the latest version of uv and set the python version to 3.13 - if: runner.os == 'Windows' - uses: astral-sh/setup-uv@v6 - with: - activate-environment: true - enable-cache: true - - name: Install python dependencies - if: runner.os == 'Windows' - run: uv sync - - - name: Set reusable strings - if: runner.os == 'Windows' - # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. - shell: bash - run: | - echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> $GITHUB_ENV - echo "pybind11_ROOT=$(python -m pybind11 --cmakedir)" >> $GITHUB_ENV - - - if: runner.os == 'Windows' - name: "Install vcpkg" - shell: bash - run: | - git clone https://github.com/microsoft/vcpkg.git - cd vcpkg - git checkout dbe35ceb30c688bf72e952ab23778e009a578f18 - echo "VCPKG_ROOT=${{ github.workspace }}/vcpkg" >> "$GITHUB_ENV" - echo "VCPKG_EXE=${{ github.workspace }}/vcpkg/vcpkg" >> "$GITHUB_ENV" - echo "${{ github.workspace }}/vcpkg" >> $GITHUB_PATH - - - if: runner.os == 'Windows' - name: "Bootstrap vcpkg" - run: | - vcpkg/bootstrap-vcpkg.bat - - - if: runner.os == 'Windows' - name: Add NuGet sources - shell: pwsh - run: | - .$(${{ env.VCPKG_EXE }} fetch nuget) ` - sources add ` - -Source "${{ env.FEED_URL }}" ` - -StorePasswordInClearText ` - -Name GitHubPackages ` - -UserName "${{ env.USERNAME }}" ` - -Password "${{ secrets.GH_PACKAGES_TOKEN }}" - .$(${{ env.VCPKG_EXE }} fetch nuget) ` - setapikey "${{ secrets.GH_PACKAGES_TOKEN }}" ` - -Source "${{ env.FEED_URL }}" - - - if: runner.os != 'Windows' - uses: nixbuild/nix-quick-install-action@v30 - # uses: cachix/install-nix-action@v31 - # with: - # nix_path: nixpkgs=channel:nixos-unstable - name: "install nix" - - - if: runner.os != 'Windows' - name: Restore uv cache - uses: actions/cache@v5 - with: - path: /tmp/.uv-cache - key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} - restore-keys: | - uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} - uv-${{ runner.os }} - - - if: runner.os != 'Windows' - uses: nicknovitski/nix-develop@v1 - env: - # Configure a constant location for the uv cache - UV_CACHE_DIR: /tmp/.uv-cache - with: - arguments: "--ignore-environment --keep ACTIONS_CACHE_URL --keep ACTIONS_RUNTIME_TOKEN --keep VCPKG_BINARY_SOURCES --keep UV_CACHE_DIR --impure" - name: "nix develop" - - - if: runner.os != 'Windows' - name: Add NuGet sources - shell: bash - run: | - mono `vcpkg fetch nuget | tail -n 1` \ - sources add \ - -Source "${{ env.FEED_URL }}" \ - -StorePasswordInClearText \ - -Name GitHubPackages \ - -UserName "${{ env.USERNAME }}" \ - -Password "${{ secrets.GH_PACKAGES_TOKEN }}" - mono `vcpkg fetch nuget | tail -n 1` \ - setapikey "${{ secrets.GH_PACKAGES_TOKEN }}" \ - -Source "${{ env.FEED_URL }}" - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - # -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - # -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - # -DVCPKG_INSTALL_OPTIONS="--debug" - run: > - cmake -B build - -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install - --preset gh-${{ runner.os }} - -S ${{ github.workspace }} - - # - name: Show error logs - # if: ${{ failure() }} - # shell: bash - # run: > - # cat /Users/runner/work/roofer/roofer/build/vcpkg-manifest-install.log - - - name: Build - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: cmake --build build --config ${{ matrix.build_type }} --verbose --preset gh-${{ runner.os }} - - - name: Test - # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --preset test-built --build-config ${{ matrix.build_type }} --test-dir build --output-on-failure - - - name: Install - run: cmake --install build --config ${{ matrix.build_type }} --verbose - - - if: runner.os == 'Windows' - name: show contents install dir - run: | - cd install - tree /F - - if: runner.os != 'Windows' - name: show contents install dir - run: | - cd install - ls -R - - name: Test installed - shell: bash - run: ctest --preset test-installed --build-config ${{ matrix.build_type }} --test-dir build --output-on-failure - - - if: runner.os != 'Windows' - name: Minimize uv cache - run: uv cache prune --ci - - - name: Set lowercase runner variables - shell: bash - run: | - echo "RUNNER_OS_LC=$(echo '${{ runner.os }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - echo "RUNNER_ARCH_LC=$(echo '${{ runner.arch }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - - - name: Upload binaries - if: startsWith(github.ref, 'refs/tags/') - uses: actions/upload-artifact@v6 - with: - name: roofer-${{ env.RUNNER_OS_LC }}-${{ env.RUNNER_ARCH_LC }}-${{ github.ref_name }} - path: | - install - retention-days: 7 From 89ab1bfb9af88f821643e54d8e148ea4c8ae1eff Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:57:54 +0000 Subject: [PATCH 37/53] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50fc9300..9f46d957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Floating point exception when the first and last point are the same in LinearRing holes - potential segfault with roofer --help +### Changed +- Revamp release build system to use conan instead of vcpkg. Easier to set up and maintain, smaller build artifacts, faster builds. + ## [1.0.0-beta.5] - 2025-08-27 ### Fixed From 9369792ba2463d728c7894c1709b3cfcad8c7638 Mon Sep 17 00:00:00 2001 From: Ravi Peters Date: Thu, 12 Mar 2026 11:58:21 +0000 Subject: [PATCH 38/53] fix install test --- tests/CMakeLists.txt | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 15e94062..11ffd686 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -116,18 +116,8 @@ if(RF_BUILD_APPS) COMMAND roofer --config "${CONFIG_DIR}/roofer-wippolder.toml" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") - add_test( - NAME "installed-reconstruct-version" - COMMAND reconstruct --version - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") - - add_test( - NAME "installed-reconstruct-wippolder" - COMMAND reconstruct --config "${CONFIG_DIR}/roofer-wippolder.toml" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") - set(tests_installed - "installed-roofer-version;installed-reconstruct-version;installed-roofer-wippolder;installed-reconstruct-wippolder" + "installed-roofer-version;installed-roofer-wippolder" ) set(WINDOWS_INSTALLED_ENVIRONMENT "GDAL_DATA=${CMAKE_INSTALL_PREFIX}\\share\\gdal;PATH=C:\\Program Files (x86)\\roofer\\bin\;$ENV{PATH}" From 9f1363a2c9fd5a61bb1166403a9f486c75787a67 Mon Sep 17 00:00:00 2001 From: ylannl Date: Sun, 22 Mar 2026 11:41:00 +0100 Subject: [PATCH 39/53] nix build: add proj,sqlite --- flake.nix | 2 ++ src/extra/io/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 3e111921..d49c66c9 100644 --- a/flake.nix +++ b/flake.nix @@ -80,6 +80,8 @@ LAStools geos gdal + proj + sqlite ] ++ lib.optionals withRerun [ rerun diff --git a/src/extra/io/CMakeLists.txt b/src/extra/io/CMakeLists.txt index ffd64e22..05d48e67 100644 --- a/src/extra/io/CMakeLists.txt +++ b/src/extra/io/CMakeLists.txt @@ -21,7 +21,7 @@ set(LIBRARY_INCLUDES find_package(laslib CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) -find_package(proj CONFIG REQUIRED) +find_package(PROJ CONFIG REQUIRED) # GDAL find_package(GDAL CONFIG REQUIRED) From 2eae8019aa105445ddf184c8641c7603a68903c6 Mon Sep 17 00:00:00 2001 From: ylannl Date: Tue, 24 Mar 2026 17:00:20 +0100 Subject: [PATCH 40/53] fix conan build on nixos --- .gitignore | 2 +- .../CMakeFiles/4.1.2/CompilerIdCXX/a.out | Bin 33736 -> 0 bytes conanfile.py | 1 + flake.nix | 3 +++ 4 files changed, 5 insertions(+), 1 deletion(-) delete mode 100755 build-conan/CMakeFiles/4.1.2/CompilerIdCXX/a.out diff --git a/.gitignore b/.gitignore index b1116ca7..18db9775 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Build dirs cmake-build-* vcpkg_installed -./build* +build* # CMake CMakeUserPresets.json diff --git a/build-conan/CMakeFiles/4.1.2/CompilerIdCXX/a.out b/build-conan/CMakeFiles/4.1.2/CompilerIdCXX/a.out deleted file mode 100755 index a166c1ca3e2a20086b85833a90d5b25a0f76cdd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33736 zcmeI*e{7RQ7zgmDKPDX@RALB=1FL_`O=NT%QDP=`jtK+8ma)V_GtQT_8x37cx>gxK zw8NmNF&V}mXfQ58<4k6P2BR?;&=`Zpgz$$fafw1A31C1I6OG`&=ehUZuI*3~jsJZw zx!yhZ+7`{GpBlew z{jUwfo9cNiz*Y7@#H*Ce)_r}u{!cu2^QgW*ohM@7oU9|?%fce>8{{fE{e3u`;M;Lc z?uGZ$y>MY|T`g}azDe(MY2kcY!9SDY`RfMyGqJwfhs-5Bo2ZJb-p`PbhQ1C;jd7?0#CL=lQAGv~8x1o9p##yg&c~ z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1R(Ig2?TiMc2ng|3jg-oD+8v;f0H{! z_d_=kg`AP%>&T_3T>p`|NA+!1v2C&LY$m2Q$E;{9O=a<9PfAfkpA}Ei0hgDCa{Q)V zd58inavp?05qauD*6NKZ>UEyFK(FxZ9MAUBuw3Jw`bY(`N*};LLwwPGebHWi_0Z@p zXV*^e86KF$``yFOJCOUAt8=b1QdVxNfz zCFTp|Sq*Z1fYf!Ily=fAb~kThhk2muD3r{YVq)j>u1bFvN^MwESFhjIJcfezR#+~7 zKl2#x_(dL*ZA-b8$C%6Y1i2Z=t?4%McvxqN%87Od18)77^=9awnvj!pS+%KbhOYfQ zex&N+M~!Dkx&mY4>)-b^UO6$~J2-IZ-iF$$$Qv8qdvf%HaO%tRJ;%IbCr@s1w}s2D z?L545`=0MUeSYP$_q=nX{E}C@@4V%U@Zjf%PJVsl^$Wib_0He+L$&L*o&{IWygd5W zoS)W>9_Tpq;fVjY-WSeRZW);NbaSOU`@oN<8W+4BUzI&~qxJu5{^;?22X@Up*R<>4 fnLk^%52dF)`pboiD`Tf`zVzJcZ%#k*ht2gD6fMp= diff --git a/conanfile.py b/conanfile.py index 34d6c1d3..d83f0e4c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -64,6 +64,7 @@ def configure(self): self.options["gdal"].with_png = False self.options["gdal"].with_qhull = False self.options["gdal"].with_sqlite3 = True + self.options["libtiff"].cxx = False self.options["libtiff"].jpeg = False self.options["gdal"].gdal_optional_drivers = False # Keep OGR optional drivers enabled: the PG driver depends on PGDump, diff --git a/flake.nix b/flake.nix index d49c66c9..12d97435 100644 --- a/flake.nix +++ b/flake.nix @@ -154,6 +154,9 @@ ++ lib.optionals stdenv.isLinux [ patchelf ]; shellHook = '' + ${pkgs.lib.optionalString pkgs.stdenv.isLinux '' + export CMAKE_LIBRARY_PATH="${pkgs.glibc.out}/lib''${CMAKE_LIBRARY_PATH:+:$CMAKE_LIBRARY_PATH}" + ''} echo "Conan dev shell ready. Run 'conan profile detect' if you haven't set up a profile yet." echo "" echo "Conan build steps (replace Release with Debug for debug build):" From 358a8eef582ab8bcfe8fe39357a8b4b127246585 Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 12:14:39 +0200 Subject: [PATCH 41/53] use cmakeCurses for conan dev shell --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 12d97435..b254b939 100644 --- a/flake.nix +++ b/flake.nix @@ -147,7 +147,7 @@ in { conan = pkgs.mkShell { buildInputs = with pkgs; [ - cmake + cmakeCurses ninja conan ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ] From 1d5a860cfcf1f3e9f258dd80981b0d348add0ada Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 12:46:12 +0200 Subject: [PATCH 42/53] remove vcpkg references --- .dockerignore | 2 - CMakeLists.txt | 20 ---------- CMakePresets.json | 65 ------------------------------- apps/roofer-app/CMakeLists.txt | 14 ------- vcpkg.json | 71 ---------------------------------- 5 files changed, 172 deletions(-) delete mode 100644 vcpkg.json diff --git a/.dockerignore b/.dockerignore index 02aca4ca..ce952dcc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,5 @@ docs experiments -external/vcpkg external/val3dity tests -vcpkg_installed diff --git a/CMakeLists.txt b/CMakeLists.txt index 168ad42a..bfaa7570 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,26 +14,6 @@ option(RF_BUILD_TESTING "Enable tests for roofer" OFF) option(RF_ENABLE_HEAP_TRACING "Enable heap allocation overloads" OFF) option(RF_USE_CPM "Use CPM to fetch dependencies" ON) -# Enable the vcpkg features that are required by the options -if(RF_USE_LOGGER_SPDLOG) - list(APPEND VCPKG_MANIFEST_FEATURES "spdlog") -endif() -if(RF_BUILD_TESTING) - list(APPEND VCPKG_MANIFEST_FEATURES "test") -endif() -if(RF_BUILD_APPS) - list(APPEND VCPKG_MANIFEST_FEATURES "apps") -endif() -if(RF_USE_VAL3DITY) - list(APPEND VCPKG_MANIFEST_FEATURES "val3dity") -endif() -if(RF_USE_RERUN) - list(APPEND VCPKG_MANIFEST_FEATURES "app-rerun") -endif() -# if(RF_BUILD_BINDINGS) -# list(APPEND VCPKG_MANIFEST_FEATURES "python") -# endif() - # TODO: add version number project(roofer VERSION 1.0.0 LANGUAGES C CXX) diff --git a/CMakePresets.json b/CMakePresets.json index f8093063..9939a00b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -14,45 +14,6 @@ "RF_BUILD_BINDINGS": "OFF" } }, - { - "name": "vcpkg-full-test", - "description": "Build with vcpkg with all dependencies, to test a full build.", - "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "RF_USE_VAL3DITY": "ON", - "RF_USE_RERUN": "OFF", - "RF_USE_LOGGER_SPDLOG": "OFF", - "RF_BUILD_TESTING": "ON", - "RF_BUILD_APPS": "ON", - "RF_BUILD_DOC_HELPER": "ON", - "RF_BUILD_BINDINGS": "ON" - } - }, - { - "name": "vcpkg-minimal", - "description": "Build with vcpkg without any optional dependencies, in order to get the quickest build possible.", - "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "RF_USE_VAL3DITY": "OFF", - "RF_USE_RERUN": "OFF", - "RF_USE_LOGGER_SPDLOG": "OFF", - "RF_BUILD_TESTING": "OFF", - "RF_BUILD_APPS": "ON", - "RF_BUILD_BINDINGS": "OFF" - } - }, - { - "name": "vcpkg-minimal-test", - "description": "Build with vcpkg without any optional dependencies, in order to get the quickest build possible for running tests.", - "inherits": "vcpkg-minimal", - "cacheVariables": { - "RF_BUILD_TESTING": "ON", - "RF_BUILD_APPS": "ON", - "RF_BUILD_BINDINGS": "ON" - } - }, { "name": "system-minimal-test", "description": "Build with system packages without any optional dependencies, in order to get the quickest build possible for running tests.", @@ -66,14 +27,6 @@ "RF_BUILD_BINDINGS": "ON" } }, - { - "name": "vcpkg-with-bindings", - "description": "Build python bindings using vcpkg and pybind as a dependency", - "inherits": "vcpkg-minimal", - "cacheVariables": { - "RF_BUILD_BINDINGS": "ON" - } - }, { "name": "vcpkg-docs", "description": "Build python bindings needed for documentation generation", @@ -88,24 +41,6 @@ "RF_BUILD_DOC_HELPER": "ON", "RF_BUILD_BINDINGS": "ON" } - }, - { - "name": "gh-Linux", - "description": "Preset for github actions CI Linux configure", - "inherits": "vcpkg-full-test" - }, - { - "name": "gh-macOS", - "description": "Preset for github actions CI macOS configure", - "inherits": "vcpkg-full-test" - }, - { - "name": "gh-Windows", - "description": "Preset for github actions CI Windows configure", - "inherits": "vcpkg-full-test", - "cacheVariables": { - "X_VCPKG_APPLOCAL_DEPS_INSTALL": "ON" - } } ], "buildPresets": [ diff --git a/apps/roofer-app/CMakeLists.txt b/apps/roofer-app/CMakeLists.txt index d621d309..140465ec 100644 --- a/apps/roofer-app/CMakeLists.txt +++ b/apps/roofer-app/CMakeLists.txt @@ -41,20 +41,6 @@ if(RF_BUILD_APPS) if(RF_GIT_HASH) target_compile_definitions(roofer PRIVATE RF_GIT_HASH="${RF_GIT_HASH}") endif() - if(DEFINED VCPKG_TARGET_TRIPLET) - if(EXISTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj") - install( - DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj - DESTINATION "share" - FILES_MATCHING PATTERN "*" PATTERN "*.cmake" EXCLUDE) - endif() - if(EXISTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/gdal") - install( - DIRECTORY ${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/gdal - DESTINATION "share" - FILES_MATCHING PATTERN "*" PATTERN "*.cmake" EXCLUDE) - endif() - endif() endif() if(RF_BUILD_DOC_HELPER) diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index 8ccd5244..00000000 --- a/vcpkg.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "roofer", - "version-string": "1.0.0", - "features": { - "spdlog": { - "description": "Use spdlog as logging backend.", - "dependencies": [ - "spdlog" - ] - }, - "test": { - "description": "Build tests.", - "dependencies": [ - "catch2" - ] - }, - "val3dity": { - "description": "Use val3dity for 3D validation.", - "dependencies": [ - "pugixml", - "spdlog", - "tclap" - ] - }, - "apps": { - "description": "Build apps.", - "dependencies": [ - "geos", - "lastools", - { - "name": "gdal", - "default-features": false, - "features": [ - "sqlite3", - "postgresql", - "geos" - ] - }, - { - "name": "bshoshany-thread-pool", - "version>=": "4.1.0" - }, - { - "name": "nlohmann-json", - "version>=": "3.11.3" - } - ] - }, - "app-rerun": { - "description": "Build with rerun support.", - "dependencies": [ - "arrow" - ] - } - }, - "dependencies": [ - { - "name": "cgal", - "version>=": "6.0" - }, - "eigen3", - "fmt" - ], - "overrides": [ - { - "name": "nlohmann-json", - "version": "3.11.3" - } - ], - "builtin-baseline": "dbe35ceb30c688bf72e952ab23778e009a578f18" -} From 6c309af996f31e7001ba37e7da11e29287b5083b Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 12:49:21 +0200 Subject: [PATCH 43/53] refactor build workflows, switch over docs flow to conan --- .github/workflows/build_install_conan.yml | 92 ++------------- .github/workflows/conan-build.yml | 134 ++++++++++++++++++++++ .github/workflows/documentation.yml | 85 ++++++-------- conanfile.py | 3 + 4 files changed, 185 insertions(+), 129 deletions(-) create mode 100644 .github/workflows/conan-build.yml diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index 0e0921b5..4506290e 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -10,88 +10,20 @@ on: jobs: build: - runs-on: ${{ matrix.os }} - strategy: fail-fast: false matrix: os: [ubuntu-24.04, windows-2022, macos-15] build_type: [Release] - - steps: - - uses: actions/checkout@v5 - - - name: Install and setup Conan - uses: conan-io/setup-conan@v1 - with: - cache_packages: true - use_venv: true - - - name: Detect Conan profile - run: conan profile detect --force - - - name: Install dependencies - run: > - conan install . - --output-folder=build - --build=missing - --settings=build_type=${{ matrix.build_type }} - --settings=compiler.cppstd=20 - --options="&:build_apps=True" - --options="&:use_spdlog=True" - --options="&:use_val3dity=False" - --options="&:build_bindings=False" - --options="&:build_testing=True" - - - name: Configure CMake - run: > - cmake -B build - -S ${{ github.workspace }} - -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" - -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install" - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DRF_BUILD_APPS=ON - -DRF_USE_LOGGER_SPDLOG=ON - -DRF_USE_VAL3DITY=OFF - -DRF_BUILD_BINDINGS=OFF - -DRF_BUILD_TESTING=ON - -DRF_TEST_INSTALL=ON - -DRF_USE_CPM=OFF - - - name: Build - run: cmake --build build --config ${{ matrix.build_type }} --verbose - - - name: Install - run: cmake --install build --config ${{ matrix.build_type }} --verbose - - - name: Test - env: - GDAL_DATA: ${{ github.workspace }}/install/share/gdal - PROJ_DATA: ${{ github.workspace }}/install/share/proj - run: ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure - - - if: runner.os == 'Windows' - name: Show install tree - run: | - cd install - tree /F - - - if: runner.os != 'Windows' - name: Show install tree - run: | - cd install - ls -R - - - name: Set lowercase runner variables - shell: bash - run: | - echo "RUNNER_OS_LC=$(echo '${{ runner.os }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - echo "RUNNER_ARCH_LC=$(echo '${{ runner.arch }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - - - name: Upload binaries - uses: actions/upload-artifact@v6 - with: - name: roofer-conan-${{ env.RUNNER_OS_LC }}-${{ env.RUNNER_ARCH_LC }}-${{ github.sha }} - path: | - install - retention-days: 7 + uses: ./.github/workflows/conan-build.yml + with: + runs_on: ${{ matrix.os }} + build_type: ${{ matrix.build_type }} + build_apps: true + use_spdlog: true + use_val3dity: false + build_bindings: false + build_testing: true + build_doc_helper: false + run_tests: true + artifact_suffix: build diff --git a/.github/workflows/conan-build.yml b/.github/workflows/conan-build.yml new file mode 100644 index 00000000..a5313852 --- /dev/null +++ b/.github/workflows/conan-build.yml @@ -0,0 +1,134 @@ +name: Conan Build + +on: + workflow_call: + inputs: + runs_on: + description: Runner label for the build job. + required: true + type: string + build_type: + description: CMake build type. + required: false + type: string + default: Release + build_apps: + description: Build the roofer CLI applications. + required: false + type: boolean + default: true + use_spdlog: + description: Enable the spdlog logging backend. + required: false + type: boolean + default: true + use_val3dity: + description: Enable val3dity support. + required: false + type: boolean + default: false + build_bindings: + description: Build the Python bindings. + required: false + type: boolean + default: false + build_testing: + description: Build and run the test suite. + required: false + type: boolean + default: false + build_doc_helper: + description: Build the documentation helper executable. + required: false + type: boolean + default: false + run_tests: + description: Run ctest after building. + required: false + type: boolean + default: false + artifact_suffix: + description: Suffix used for the uploaded artifact name. + required: false + type: string + default: build + outputs: + artifact_name: + description: Uploaded build artifact name. + value: ${{ jobs.build.outputs.artifact_name }} + +jobs: + build: + runs-on: ${{ inputs.runs_on }} + outputs: + artifact_name: ${{ steps.meta.outputs.artifact_name }} + + steps: + - uses: actions/checkout@v5 + + - name: Install and setup Conan + uses: conan-io/setup-conan@v1 + with: + home: ${{ runner.temp }}/conan + cache_packages: true + use_venv: true + + - name: Detect Conan profile + run: conan profile detect --force + + - name: Install dependencies + run: > + conan install . + --output-folder=build + --build=missing + --settings=build_type=${{ inputs.build_type }} + --settings=compiler.cppstd=20 + --options="&:build_apps=${{ inputs.build_apps && 'True' || 'False' }}" + --options="&:use_spdlog=${{ inputs.use_spdlog && 'True' || 'False' }}" + --options="&:use_val3dity=${{ inputs.use_val3dity && 'True' || 'False' }}" + --options="&:build_bindings=${{ inputs.build_bindings && 'True' || 'False' }}" + --options="&:build_testing=${{ inputs.build_testing && 'True' || 'False' }}" + + - name: Configure CMake + run: > + cmake -B build + -S ${{ github.workspace }} + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" + -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install" + -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} + -DRF_BUILD_APPS=${{ inputs.build_apps && 'ON' || 'OFF' }} + -DRF_USE_LOGGER_SPDLOG=${{ inputs.use_spdlog && 'ON' || 'OFF' }} + -DRF_USE_VAL3DITY=${{ inputs.use_val3dity && 'ON' || 'OFF' }} + -DRF_BUILD_BINDINGS=${{ inputs.build_bindings && 'ON' || 'OFF' }} + -DRF_BUILD_TESTING=${{ inputs.build_testing && 'ON' || 'OFF' }} + -DRF_BUILD_DOC_HELPER=${{ inputs.build_doc_helper && 'ON' || 'OFF' }} + -DRF_TEST_INSTALL=${{ inputs.run_tests && 'ON' || 'OFF' }} + -DRF_USE_CPM=OFF + + - name: Build + run: cmake --build build --config ${{ inputs.build_type }} --verbose + + - name: Install + run: cmake --install build --config ${{ inputs.build_type }} --verbose + + - name: Test + if: ${{ inputs.run_tests }} + env: + GDAL_DATA: ${{ github.workspace }}/install/share/gdal + PROJ_DATA: ${{ github.workspace }}/install/share/proj + run: ctest --test-dir build --build-config ${{ inputs.build_type }} --output-on-failure + + - name: Set artifact name + id: meta + shell: bash + run: | + echo "artifact_name=roofer-conan-${{ runner.os }}-${{ runner.arch }}-${{ inputs.artifact_suffix }}" >> "$GITHUB_OUTPUT" + + - name: Upload build artifact + uses: actions/upload-artifact@v6 + with: + name: ${{ steps.meta.outputs.artifact_name }} + path: | + build + install + retention-days: 7 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index f9bd006d..14f72503 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,31 +8,33 @@ on: # branches: ["develop"] jobs: + build: + uses: ./.github/workflows/conan-build.yml + with: + runs_on: ubuntu-latest + build_type: Release + build_apps: false + use_spdlog: false + use_val3dity: false + build_bindings: true + build_testing: false + build_doc_helper: true + run_tests: false + artifact_suffix: docs + deploy: name: Build and publish documentation runs-on: ubuntu-latest + needs: build + + env: + UV_CACHE_DIR: /tmp/.uv-cache permissions: contents: write - env: - USERNAME: 3DBAG - FEED_URL: https://nuget.pkg.github.com/3DBAG/index.json - VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/3DBAG/index.json,readwrite" - steps: - uses: actions/checkout@v5 - - uses: actions/github-script@v7 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - uses: nixbuild/nix-quick-install-action@v30 - # uses: cachix/install-nix-action@v31 - # with: - # nix_path: nixpkgs=channel:nixos-unstable - name: "install nix" - name: Restore uv cache uses: actions/cache@v5 @@ -43,47 +45,32 @@ jobs: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} uv-${{ runner.os }} - - uses: nicknovitski/nix-develop@v1 - env: - # Configure a constant location for the uv cache - UV_CACHE_DIR: /tmp/.uv-cache + - name: Download Conan build artifact + uses: actions/download-artifact@v4 with: - arguments: "--ignore-environment --keep ACTIONS_CACHE_URL --keep ACTIONS_RUNTIME_TOKEN --keep VCPKG_BINARY_SOURCES --keep UV_CACHE_DIR --impure" - name: "nix develop" + name: ${{ needs.build.outputs.artifact_name }} + path: ${{ github.workspace }} - - name: Add NuGet sources + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install docs tooling shell: bash run: | - mono `vcpkg fetch nuget | tail -n 1` \ - sources add \ - -Source "${{ env.FEED_URL }}" \ - -StorePasswordInClearText \ - -Name GitHubPackages \ - -UserName "${{ env.USERNAME }}" \ - -Password "${{ secrets.GH_PACKAGES_TOKEN }}" - mono `vcpkg fetch nuget | tail -n 1` \ - setapikey "${{ secrets.GH_PACKAGES_TOKEN }}" \ - -Source "${{ env.FEED_URL }}" - - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: > - cmake -B build - --preset vcpkg-docs - -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install - -S ${{ github.workspace }} + sudo apt-get update + sudo apt-get install -y doxygen + python -m pip install --upgrade pip uv - - name: Build bindings - # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). - run: | - cmake --build build --config Release --verbose --preset gh-${{ runner.os }} - cmake --install build --config Release --verbose + - name: Sync Python dependencies + shell: bash + run: uv sync --frozen - name: Generate Docs + shell: bash run: | - cd docs - make html + uv run -- make -C docs html - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 diff --git a/conanfile.py b/conanfile.py index d83f0e4c..23423af4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -39,6 +39,9 @@ def requirements(self): # bshoshany-thread-pool is header-only, not yet in ConanCenter; # leave as CPM fallback for now (RF_USE_CPM=ON) + if self.options.build_bindings: + self.requires("pybind11/3.0.1") + if self.options.use_val3dity: self.requires("pugixml/1.15") self.requires("tclap/1.2.5") From 12b45ac94238214912b57db9cf406cf9022e7db0 Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 12:58:33 +0200 Subject: [PATCH 44/53] update build instructions to use conan/nix, not vcpkg --- CHANGELOG.md | 3 +- CMakePresets.json | 19 +------ README-dev.md | 54 ++++++++++++++++++- docs/developers.md | 122 ++++++++++++++++++++++++++++++++++--------- flake.nix | 2 +- rooferpy/README.md | 47 +++++++++++++++-- tests/CMakeLists.txt | 22 +++----- 7 files changed, 203 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f46d957..066d4324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - potential segfault with roofer --help ### Changed -- Revamp release build system to use conan instead of vcpkg. Easier to set up and maintain, smaller build artifacts, faster builds. +- Revamp recommended build system to use conan instead of vcpkg. Easier to set up and maintain, smaller build artifacts, faster builds. +- Add support for Nix builds ## [1.0.0-beta.5] - 2025-08-27 diff --git a/CMakePresets.json b/CMakePresets.json index 9939a00b..18688173 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -27,21 +27,6 @@ "RF_BUILD_BINDINGS": "ON" } }, - { - "name": "vcpkg-docs", - "description": "Build python bindings needed for documentation generation", - "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "RF_USE_VAL3DITY": "OFF", - "RF_USE_RERUN": "OFF", - "RF_USE_LOGGER_SPDLOG": "OFF", - "RF_BUILD_TESTING": "OFF", - "RF_BUILD_APPS": "OFF", - "RF_BUILD_DOC_HELPER": "ON", - "RF_BUILD_BINDINGS": "ON" - } - } ], "buildPresets": [ { @@ -68,7 +53,7 @@ { "name": "test-built", "description": "Test the compiled targets in their build directory", - "configurePreset": "vcpkg-minimal-test", + "configurePreset": "conan-release", "output": { "outputOnFailure": true, "verbosity": "verbose" @@ -82,7 +67,7 @@ { "name": "test-installed", "description": "Test the installed artifacts", - "configurePreset": "vcpkg-minimal-test", + "configurePreset": "conan-release", "output": { "outputOnFailure": true, "verbosity": "verbose" diff --git a/README-dev.md b/README-dev.md index 75555bba..06048b0a 100644 --- a/README-dev.md +++ b/README-dev.md @@ -108,13 +108,65 @@ add_test( ## Documentation -To build the documentation locally: +To build the documentation locally, first build the `rooferpy` module and `doc-helper` executable. +The recommended path is Conan: ```shell +conan profile detect --force +conan install . \ + --output-folder=build \ + --build=missing \ + --settings=build_type=Release \ + --settings=compiler.cppstd=20 \ + --options="&:build_apps=False" \ + --options="&:use_spdlog=False" \ + --options="&:use_val3dity=False" \ + --options="&:build_bindings=True" \ + --options="&:build_testing=False" +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PWD/install \ + -DRF_BUILD_APPS=OFF \ + -DRF_USE_LOGGER_SPDLOG=OFF \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=ON \ + -DRF_BUILD_TESTING=OFF \ + -DRF_BUILD_DOC_HELPER=ON \ + -DRF_USE_CPM=OFF +cmake --build build --target rooferpy doc-helper +cmake --install build cd docs make html ``` +If you prefer Nix, you can do the same with the `nix develop` shell: + +```shell +nix develop +cmake -S . -B build \ + -G Ninja \ + -DRF_BUILD_APPS=OFF \ + -DRF_USE_LOGGER_SPDLOG=OFF \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=ON \ + -DRF_BUILD_TESTING=OFF \ + -DRF_BUILD_DOC_HELPER=ON \ + -DRF_USE_CPM=OFF +cmake --build build --target rooferpy doc-helper +cmake --install build +cd docs +make html +``` + +If you want the packaged Nix outputs instead of a local build tree, use: + +```shell +nix build .#default +nix build .#rooferpy +``` + The rendered documentation is in the `docs/html` directory, and the main page is `docs/html/index.html`. #### Documenting the code diff --git a/docs/developers.md b/docs/developers.md index 17c2c3bd..55f9b1b6 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -1,51 +1,123 @@ # Building from source -## Compilation with Nix +## Compilation with Conan -The easiest way to get all the required dependencies to build roofer is to use [Nix](https://nixos.org). To install nix you can use the [install script from Determinate Systems](https://zero-to-nix.com/start/install/#run). At this moment Nix only works on Linux and macOS. +Conan is the recommended way to build roofer from source. -Once Nix is installed you can setup the development environment and build roofer like this: +Install Conan 2 and then configure and build the project like this: ```sh git clone https://github.com/3DBAG/roofer.git cd roofer -nix develop -mkdir build -cmake --preset vcpkg-minimal -S . -B build +conan profile detect --force +conan install . \ + --output-folder=build \ + --build=missing \ + --settings=build_type=Release \ + --settings=compiler.cppstd=20 \ + --options="&:build_apps=True" \ + --options="&:use_spdlog=True" \ + --options="&:use_val3dity=False" \ + --options="&:build_bindings=False" \ + --options="&:build_testing=False" +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PWD/install \ + -DRF_BUILD_APPS=ON \ + -DRF_USE_LOGGER_SPDLOG=ON \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=OFF \ + -DRF_BUILD_TESTING=OFF \ + -DRF_USE_CPM=OFF cmake --build build # Optionally, install roofer cmake --install build ``` -## Compilation without Nix +## Compilation with Nix -It is recommended to use [vcpkg](https://vcpkg.io) to build **roofer**. +If you prefer Nix, you can use the provided development shell. At this moment Nix only works on Linux and macOS. -Follow the [vcpkg instructions](https://learn.microsoft.com/en-gb/vcpkg/get_started/get-started?pivots=shell-cmd) to set it up. +Once Nix is installed you can set up the development environment and build roofer like this: -After *vcpkg* is set up, set the ``VCPKG_ROOT`` environment variable to point to the directory where vcpkg is installed. +```sh +git clone https://github.com/3DBAG/roofer.git +cd roofer +nix develop +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DRF_BUILD_APPS=ON \ + -DRF_USE_LOGGER_SPDLOG=ON \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=OFF \ + -DRF_BUILD_TESTING=OFF \ + -DRF_USE_CPM=OFF +cmake --build build +# Optionally, install roofer +cmake --install build +``` -On *macOS* you need to install additional build tools: +If you just want the packaged build outputs, `nix build` also works: -```{code-block} shell -brew install autoconf autoconf-archive automake libtool -export PATH="/opt/homebrew/opt/m4/bin:$PATH" +```sh +nix build .#default +nix build .#rooferpy ``` -On *Ubuntu* you need to install additional build tools: +## Documentation + +To build the documentation locally, first build the documentation helper and Python bindings. + +### With Conan -```{code-block} shell -apt install autoconf bison flex libtool +```sh +conan profile detect --force +conan install . \ + --output-folder=build \ + --build=missing \ + --settings=build_type=Release \ + --settings=compiler.cppstd=20 \ + --options="&:build_apps=False" \ + --options="&:use_spdlog=False" \ + --options="&:use_val3dity=False" \ + --options="&:build_bindings=True" \ + --options="&:build_testing=False" +cmake -S . -B build \ + -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$PWD/install \ + -DRF_BUILD_APPS=OFF \ + -DRF_USE_LOGGER_SPDLOG=OFF \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=ON \ + -DRF_BUILD_TESTING=OFF \ + -DRF_BUILD_DOC_HELPER=ON \ + -DRF_USE_CPM=OFF +cmake --build build --target rooferpy doc-helper +cmake --install build +cd docs +make html ``` -Clone the roofer repository and use one of the CMake presets to build the roofer. +### With Nix -```{code-block} shell -git clone https://github.com/3DBAG/roofer.git -cd roofer -mkdir build -cmake --preset vcpkg-minimal -S . -B build -cmake --build build -# Optionally, install roofer +```sh +nix develop +cmake -S . -B build \ + -G Ninja \ + -DRF_BUILD_APPS=OFF \ + -DRF_USE_LOGGER_SPDLOG=OFF \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=ON \ + -DRF_BUILD_TESTING=OFF \ + -DRF_BUILD_DOC_HELPER=ON \ + -DRF_USE_CPM=OFF +cmake --build build --target rooferpy doc-helper cmake --install build +cd docs +make html ``` diff --git a/flake.nix b/flake.nix index b254b939..f32f0efd 100644 --- a/flake.nix +++ b/flake.nix @@ -219,7 +219,7 @@ uv sync source .venv/bin/activate export pybind11_DIR="$(python -m pybind11 --cmakedir)" - echo "Roofer dev shell with vcpkg is ready" + echo "Roofer dev shell with Nix is ready" ''; }; }); diff --git a/rooferpy/README.md b/rooferpy/README.md index ee6158cd..e337d192 100644 --- a/rooferpy/README.md +++ b/rooferpy/README.md @@ -1,11 +1,48 @@ # Python bindings for roofer C++ API -We use pybind11 for python bindings. To use the bindings, make sure to [install pybind11](https://pybind11.readthedocs.io/en/latest/installing.html#include-with-pypi) and compile the source using the `vcpkg-with-bindings` preset, i.e. +We use pybind11 for python bindings. To use the bindings, build with either Conan or Nix. + +With Conan: + +``` +cd roofer-dev +conan profile detect --force +conan install . \ + --output-folder=build_python \ + --build=missing \ + --settings=build_type=Release \ + --settings=compiler.cppstd=20 \ + --options="&:build_apps=False" \ + --options="&:use_spdlog=False" \ + --options="&:use_val3dity=False" \ + --options="&:build_bindings=True" \ + --options="&:build_testing=False" +cmake -S . -B build_python \ + -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=build_python/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DRF_BUILD_APPS=OFF \ + -DRF_USE_LOGGER_SPDLOG=OFF \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=ON \ + -DRF_BUILD_TESTING=OFF \ + -DRF_USE_CPM=OFF +cmake --build build_python --target rooferpy +``` + +With Nix: ``` cd roofer-dev -mkdir build_python -cmake --preset vcpkg-with-bindings -S . -B build_python -cmake --build build_python +nix develop +cmake -S . -B build_python \ + -G Ninja \ + -DRF_BUILD_APPS=OFF \ + -DRF_USE_LOGGER_SPDLOG=OFF \ + -DRF_USE_VAL3DITY=OFF \ + -DRF_BUILD_BINDINGS=ON \ + -DRF_BUILD_TESTING=OFF \ + -DRF_USE_CPM=OFF +cmake --build build_python --target rooferpy ``` -The rooferpy library will be located in `build_python/rooferpy/roofer.cpyton-.so`. Import the .so file (e.g. place it in the same folder as .py script) to use roofer python API. +The rooferpy library will be located in `build_python/rooferpy/roofer.cpython-.so`. Import the .so file (e.g. place it in the same folder as .py script) to use roofer python API. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 11ffd686..902d2425 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,11 +6,9 @@ include(FetchContent) set(CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/config") set(DATA_URL_ROOT "https://data.3dbag.nl/testdata/roofer") set(DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(DEFINED VCPKG_TOOLCHAIN) - set(TEST_ENVIRONMENT - "PROJ_DATA=${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/proj;GDAL_DATA=${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/share/gdal" - ) -endif() +set(TEST_ENVIRONMENT + "PROJ_DATA=${CMAKE_INSTALL_PREFIX}/share/proj;GDAL_DATA=${CMAKE_INSTALL_PREFIX}/share/gdal" +) FetchContent_Declare( wippolder @@ -102,9 +100,7 @@ if(RF_BUILD_APPS) # Integration tests that are run on the installed artifacts must be prefixed # with "installed-". Note that these tests must be invoked *after* the - # artifacts were installed. We don't set the TEST_ENVIRONMENT for the - # installed apps, because the required paths are supposed to be set by the - # install process. + # artifacts were installed. if(RF_TEST_INSTALL) add_test( NAME "installed-roofer-version" @@ -119,14 +115,8 @@ if(RF_BUILD_APPS) set(tests_installed "installed-roofer-version;installed-roofer-wippolder" ) - set(WINDOWS_INSTALLED_ENVIRONMENT - "GDAL_DATA=${CMAKE_INSTALL_PREFIX}\\share\\gdal;PATH=C:\\Program Files (x86)\\roofer\\bin\;$ENV{PATH}" - ) - if(WIN32) - set_tests_properties( - ${tests_installed} PROPERTIES ENVIRONMENT - "${WINDOWS_INSTALLED_ENVIRONMENT}") - endif() + set_tests_properties( + ${tests_installed} PROPERTIES ENVIRONMENT "${TEST_ENVIRONMENT}") endif() endif() From b4cac49c07aa9bcdf10c3b38d3a0493787499f82 Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 13:23:30 +0200 Subject: [PATCH 45/53] use build presets for gh runners, test doc workflow --- .github/workflows/build-push-docker.yml | 2 -- .github/workflows/build_install_conan.yml | 13 +++++++++++-- .github/workflows/conan-build.yml | 6 +++++- .github/workflows/documentation.yml | 7 ++++--- CMakePresets.json | 6 +++--- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index 9db34b6e..828b052d 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -7,8 +7,6 @@ on: paths-ignore: - "**.md" - "**.rst" - branches: - - develop pull_request: branches: - develop diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index 4506290e..cf8f29c5 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -13,12 +13,21 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04, windows-2022, macos-15] - build_type: [Release] + include: + - os: ubuntu-22.04 + build_type: Release + build_preset: gh-Linux + - os: windows-2022 + build_type: Release + build_preset: gh-Windows + - os: macos-15 + build_type: Release + build_preset: gh-macOS uses: ./.github/workflows/conan-build.yml with: runs_on: ${{ matrix.os }} build_type: ${{ matrix.build_type }} + build_preset: ${{ matrix.build_preset }} build_apps: true use_spdlog: true use_val3dity: false diff --git a/.github/workflows/conan-build.yml b/.github/workflows/conan-build.yml index a5313852..d3803d2b 100644 --- a/.github/workflows/conan-build.yml +++ b/.github/workflows/conan-build.yml @@ -12,6 +12,10 @@ on: required: false type: string default: Release + build_preset: + description: CMake build preset name for the parallel build step. + required: true + type: string build_apps: description: Build the roofer CLI applications. required: false @@ -106,7 +110,7 @@ jobs: -DRF_USE_CPM=OFF - name: Build - run: cmake --build build --config ${{ inputs.build_type }} --verbose + run: cmake --build --preset ${{ inputs.build_preset }} --verbose - name: Install run: cmake --install build --config ${{ inputs.build_type }} --verbose diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 14f72503..1319e806 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -4,15 +4,16 @@ on: push: branches: - "main" - # pull_request: - # branches: ["develop"] + pull_request: + branches: ["develop"] jobs: build: uses: ./.github/workflows/conan-build.yml with: - runs_on: ubuntu-latest + runs_on: ubuntu-22.04 build_type: Release + build_preset: gh-Linux build_apps: false use_spdlog: false use_val3dity: false diff --git a/CMakePresets.json b/CMakePresets.json index 18688173..eff5989e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -32,19 +32,19 @@ { "name": "gh-Linux", "description": "Preset for github actions CI Linux build", - "configurePreset": "gh-Linux", + "configurePreset": "conan-release", "jobs": 4 }, { "name": "gh-macOS", "description": "Preset for github actions CI macOS build", - "configurePreset": "gh-macOS", + "configurePreset": "conan-release", "jobs": 3 }, { "name": "gh-Windows", "description": "Preset for github actions CI Windows build", - "configurePreset": "gh-Windows", + "configurePreset": "conan-release", "jobs": 4, "configuration": "Release" } From 86e0961708c5c9385092fab7c3e74f451cdd59b3 Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 13:27:45 +0200 Subject: [PATCH 46/53] fix sytax error in cmakePresets --- .github/workflows/documentation.yml | 4 ++-- CMakePresets.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 1319e806..fabfff75 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -4,8 +4,8 @@ on: push: branches: - "main" - pull_request: - branches: ["develop"] + pull_request: + branches: ["develop"] jobs: build: diff --git a/CMakePresets.json b/CMakePresets.json index eff5989e..d7750bd0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -26,7 +26,7 @@ "RF_BUILD_APPS": "ON", "RF_BUILD_BINDINGS": "ON" } - }, + } ], "buildPresets": [ { From e883ef30613c620103f0dbe43d44e65a63dacfa1 Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 13:36:08 +0200 Subject: [PATCH 47/53] fix CI error --- CMakePresets.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index d7750bd0..f20d0994 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -26,25 +26,32 @@ "RF_BUILD_APPS": "ON", "RF_BUILD_BINDINGS": "ON" } + }, + { + "name": "gh-conan-release", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } } ], "buildPresets": [ { "name": "gh-Linux", "description": "Preset for github actions CI Linux build", - "configurePreset": "conan-release", + "configurePreset": "gh-conan-release", "jobs": 4 }, { "name": "gh-macOS", "description": "Preset for github actions CI macOS build", - "configurePreset": "conan-release", + "configurePreset": "gh-conan-release", "jobs": 3 }, { "name": "gh-Windows", "description": "Preset for github actions CI Windows build", - "configurePreset": "conan-release", + "configurePreset": "gh-conan-release", "jobs": 4, "configuration": "Release" } @@ -53,7 +60,7 @@ { "name": "test-built", "description": "Test the compiled targets in their build directory", - "configurePreset": "conan-release", + "configurePreset": "gh-conan-release", "output": { "outputOnFailure": true, "verbosity": "verbose" @@ -67,7 +74,7 @@ { "name": "test-installed", "description": "Test the installed artifacts", - "configurePreset": "conan-release", + "configurePreset": "gh-conan-release", "output": { "outputOnFailure": true, "verbosity": "verbose" From c964e0aa920c8daf4cad5cf62ad7beca050ee323 Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 14:26:18 +0200 Subject: [PATCH 48/53] attempt to fix ubunut 22 runner build --- include/roofer/common/common.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/roofer/common/common.hpp b/include/roofer/common/common.hpp index 117e3de2..66771566 100644 --- a/include/roofer/common/common.hpp +++ b/include/roofer/common/common.hpp @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include #include From 44c0550fe5b0fec6c40b364ad50248affc545aaf Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 14:40:22 +0200 Subject: [PATCH 49/53] switch back to ubuntu 24 gh runner --- .github/workflows/build_install_conan.yml | 2 +- .github/workflows/documentation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index cf8f29c5..feee3fcd 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 + - os: ubuntu-24.04 build_type: Release build_preset: gh-Linux - os: windows-2022 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index fabfff75..e88658bf 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -11,7 +11,7 @@ jobs: build: uses: ./.github/workflows/conan-build.yml with: - runs_on: ubuntu-22.04 + runs_on: ubuntu-24.04 build_type: Release build_preset: gh-Linux build_apps: false From 3cee9fd1926c225e5dcfe11774d1a24ac069249e Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 14:44:50 +0200 Subject: [PATCH 50/53] fixes for doc workflow --- .github/workflows/documentation.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index e88658bf..fd663d4c 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -52,6 +52,10 @@ jobs: name: ${{ needs.build.outputs.artifact_name }} path: ${{ github.workspace }} + - name: Restore doc-helper execute bit + shell: bash + run: chmod +x build/apps/roofer-app/doc-helper + - name: Set up Python uses: actions/setup-python@v5 with: From 337d21532e22321b9fe28d54c3c466f97d7048cd Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 15:08:46 +0200 Subject: [PATCH 51/53] simplify and improve build/workflows --- .github/workflows/build-push-docker.yml | 25 +++++++++++++++++ .github/workflows/build_install_conan.yml | 11 +++++--- .github/workflows/conan-build.yml | 17 +++++------- .github/workflows/cpp-linter.yml | 8 +++--- .github/workflows/documentation.yml | 33 +++++++++++++++++++---- .github/workflows/pre-commit.yml | 3 +++ CMakePresets.json | 21 --------------- README-dev.md | 9 ++----- docs/developers.md | 18 +++---------- 9 files changed, 80 insertions(+), 65 deletions(-) diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index 828b052d..b9723de9 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -14,21 +14,46 @@ on: jobs: build: runs-on: ubuntu-latest + permissions: + contents: read + steps: - uses: actions/checkout@v5 + - name: Set docker image tag to 'develop' run: echo "DOCKER_TAG=develop" >> $GITHUB_ENV + - name: Set docker image tag to git tag if: startsWith(github.ref, 'refs/tags/v') run: echo "DOCKER_TAG=${{ github.ref_name }}" >> $GITHUB_ENV + - name: Login to Docker Hub + if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Set up Docker Buildx + id: buildx uses: docker/setup-buildx-action@v3 + + - name: Build + if: github.event_name == 'pull_request' + uses: docker/build-push-action@v6 + with: + context: ./ + file: ./docker/Dockerfile + builder: ${{ steps.buildx.outputs.name }} + build-args: | + JOBS=2 + VERSION=${{ env.DOCKER_TAG }} + push: false + tags: 3dgi/roofer:${{ env.DOCKER_TAG }} + cache-from: type=registry,ref=3dgi/roofer:buildcache + - name: Build and push + if: github.event_name != 'pull_request' uses: docker/build-push-action@v6 with: context: ./ diff --git a/.github/workflows/build_install_conan.yml b/.github/workflows/build_install_conan.yml index feee3fcd..b883f340 100644 --- a/.github/workflows/build_install_conan.yml +++ b/.github/workflows/build_install_conan.yml @@ -8,6 +8,9 @@ on: branches: ["develop"] workflow_dispatch: +permissions: + contents: read + jobs: build: strategy: @@ -16,18 +19,18 @@ jobs: include: - os: ubuntu-24.04 build_type: Release - build_preset: gh-Linux + build_jobs: 4 - os: windows-2022 build_type: Release - build_preset: gh-Windows + build_jobs: 4 - os: macos-15 build_type: Release - build_preset: gh-macOS + build_jobs: 3 uses: ./.github/workflows/conan-build.yml with: runs_on: ${{ matrix.os }} build_type: ${{ matrix.build_type }} - build_preset: ${{ matrix.build_preset }} + build_jobs: ${{ matrix.build_jobs }} build_apps: true use_spdlog: true use_val3dity: false diff --git a/.github/workflows/conan-build.yml b/.github/workflows/conan-build.yml index d3803d2b..09117770 100644 --- a/.github/workflows/conan-build.yml +++ b/.github/workflows/conan-build.yml @@ -12,10 +12,10 @@ on: required: false type: string default: Release - build_preset: - description: CMake build preset name for the parallel build step. + build_jobs: + description: Number of parallel build jobs. required: true - type: string + type: number build_apps: description: Build the roofer CLI applications. required: false @@ -64,6 +64,9 @@ on: jobs: build: runs-on: ${{ inputs.runs_on }} + permissions: + contents: read + outputs: artifact_name: ${{ steps.meta.outputs.artifact_name }} @@ -100,17 +103,11 @@ jobs: -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install" -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} - -DRF_BUILD_APPS=${{ inputs.build_apps && 'ON' || 'OFF' }} - -DRF_USE_LOGGER_SPDLOG=${{ inputs.use_spdlog && 'ON' || 'OFF' }} - -DRF_USE_VAL3DITY=${{ inputs.use_val3dity && 'ON' || 'OFF' }} - -DRF_BUILD_BINDINGS=${{ inputs.build_bindings && 'ON' || 'OFF' }} - -DRF_BUILD_TESTING=${{ inputs.build_testing && 'ON' || 'OFF' }} -DRF_BUILD_DOC_HELPER=${{ inputs.build_doc_helper && 'ON' || 'OFF' }} -DRF_TEST_INSTALL=${{ inputs.run_tests && 'ON' || 'OFF' }} - -DRF_USE_CPM=OFF - name: Build - run: cmake --build --preset ${{ inputs.build_preset }} --verbose + run: cmake --build build --config ${{ inputs.build_type }} --parallel ${{ inputs.build_jobs }} --verbose - name: Install run: cmake --install build --config ${{ inputs.build_type }} --verbose diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml index 26389a5c..0898baee 100644 --- a/.github/workflows/cpp-linter.yml +++ b/.github/workflows/cpp-linter.yml @@ -10,7 +10,8 @@ jobs: runs-on: ubuntu-latest permissions: - contents: write + contents: read + pull-requests: read steps: - uses: actions/checkout@v5 @@ -25,9 +26,8 @@ jobs: files-changed-only: false thread-comments: false - - name: Fail fast?! + - name: Fail on linter issues if: steps.linter.outputs.checks-failed != 0 run: | echo "some linter checks failed. ${{ steps.linter.outputs.checks-failed }}" - # for actual deployment - # run: exit 1 + exit 1 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index fd663d4c..f297208c 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -13,7 +13,7 @@ jobs: with: runs_on: ubuntu-24.04 build_type: Release - build_preset: gh-Linux + build_jobs: 4 build_apps: false use_spdlog: false use_val3dity: false @@ -23,8 +23,8 @@ jobs: run_tests: false artifact_suffix: docs - deploy: - name: Build and publish documentation + generate: + name: Generate documentation runs-on: ubuntu-latest needs: build @@ -32,7 +32,7 @@ jobs: UV_CACHE_DIR: /tmp/.uv-cache permissions: - contents: write + contents: read steps: - uses: actions/checkout@v5 @@ -77,10 +77,33 @@ jobs: run: | uv run -- make -C docs html + - name: Upload documentation artifact + uses: actions/upload-artifact@v6 + with: + name: docs-html + path: ${{ github.workspace }}/docs/_build/html + retention-days: 7 + + deploy: + name: Publish documentation + runs-on: ubuntu-latest + needs: generate + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + permissions: + contents: write + + steps: + - name: Download documentation artifact + uses: actions/download-artifact@v4 + with: + name: docs-html + path: ${{ github.workspace }}/docs-html + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ${{ github.workspace }}/docs/_build/html + publish_dir: ${{ github.workspace }}/docs-html # destination_dir: dev publish_branch: gh-pages diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 193f64ac..0c3a3b4f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -4,6 +4,9 @@ on: pull_request: branches: ["develop"] +permissions: + contents: read + jobs: pre-commit: runs-on: ubuntu-latest diff --git a/CMakePresets.json b/CMakePresets.json index f20d0994..515563d6 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -35,27 +35,6 @@ } } ], - "buildPresets": [ - { - "name": "gh-Linux", - "description": "Preset for github actions CI Linux build", - "configurePreset": "gh-conan-release", - "jobs": 4 - }, - { - "name": "gh-macOS", - "description": "Preset for github actions CI macOS build", - "configurePreset": "gh-conan-release", - "jobs": 3 - }, - { - "name": "gh-Windows", - "description": "Preset for github actions CI Windows build", - "configurePreset": "gh-conan-release", - "jobs": 4, - "configuration": "Release" - } - ], "testPresets": [ { "name": "test-built", diff --git a/README-dev.md b/README-dev.md index 06048b0a..9f2d3303 100644 --- a/README-dev.md +++ b/README-dev.md @@ -123,18 +123,13 @@ conan install . \ --options="&:use_val3dity=False" \ --options="&:build_bindings=True" \ --options="&:build_testing=False" +# The Conan options above are forwarded to CMake by the generated toolchain. cmake -S . -B build \ -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=$PWD/install \ - -DRF_BUILD_APPS=OFF \ - -DRF_USE_LOGGER_SPDLOG=OFF \ - -DRF_USE_VAL3DITY=OFF \ - -DRF_BUILD_BINDINGS=ON \ - -DRF_BUILD_TESTING=OFF \ - -DRF_BUILD_DOC_HELPER=ON \ - -DRF_USE_CPM=OFF + -DRF_BUILD_DOC_HELPER=ON cmake --build build --target rooferpy doc-helper cmake --install build cd docs diff --git a/docs/developers.md b/docs/developers.md index 55f9b1b6..6a216293 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -20,17 +20,12 @@ conan install . \ --options="&:use_val3dity=False" \ --options="&:build_bindings=False" \ --options="&:build_testing=False" +# The Conan options above are forwarded to CMake by the generated toolchain. cmake -S . -B build \ -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=$PWD/install \ - -DRF_BUILD_APPS=ON \ - -DRF_USE_LOGGER_SPDLOG=ON \ - -DRF_USE_VAL3DITY=OFF \ - -DRF_BUILD_BINDINGS=OFF \ - -DRF_BUILD_TESTING=OFF \ - -DRF_USE_CPM=OFF + -DCMAKE_INSTALL_PREFIX=$PWD/install cmake --build build # Optionally, install roofer cmake --install build @@ -85,18 +80,13 @@ conan install . \ --options="&:use_val3dity=False" \ --options="&:build_bindings=True" \ --options="&:build_testing=False" +# The Conan options above are forwarded to CMake by the generated toolchain. cmake -S . -B build \ -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=$PWD/install \ - -DRF_BUILD_APPS=OFF \ - -DRF_USE_LOGGER_SPDLOG=OFF \ - -DRF_USE_VAL3DITY=OFF \ - -DRF_BUILD_BINDINGS=ON \ - -DRF_BUILD_TESTING=OFF \ - -DRF_BUILD_DOC_HELPER=ON \ - -DRF_USE_CPM=OFF + -DRF_BUILD_DOC_HELPER=ON cmake --build build --target rooferpy doc-helper cmake --install build cd docs From 9ea7c1bd1141430acdaf8527b855bc49b111e26e Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 15:22:22 +0200 Subject: [PATCH 52/53] fix docs workflow --- .github/workflows/conan-build.yml | 1 + CMakeLists.txt | 7 ++++--- README-dev.md | 2 +- conanfile.py | 12 ++++++------ docs/developers.md | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/conan-build.yml b/.github/workflows/conan-build.yml index 09117770..36625311 100644 --- a/.github/workflows/conan-build.yml +++ b/.github/workflows/conan-build.yml @@ -97,6 +97,7 @@ jobs: --options="&:build_testing=${{ inputs.build_testing && 'True' || 'False' }}" - name: Configure CMake + # Conan forwards package options to the matching RF_* CMake options. run: > cmake -B build -S ${{ github.workspace }} diff --git a/CMakeLists.txt b/CMakeLists.txt index bfaa7570..420d48ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ # FetchContent-related changes cmake_minimum_required(VERSION 3.25) +# Load the toolchain before declaring project options, so Conan-provided +# variables can override option defaults via CMP0077. +project(roofer VERSION 1.0.0 LANGUAGES C CXX) + # Options option(RF_USE_LOGGER_SPDLOG "Use spdlog as logging backend." OFF) option(RF_USE_RERUN "Enable features dependent on Rerun" OFF) @@ -14,9 +18,6 @@ option(RF_BUILD_TESTING "Enable tests for roofer" OFF) option(RF_ENABLE_HEAP_TRACING "Enable heap allocation overloads" OFF) option(RF_USE_CPM "Use CPM to fetch dependencies" ON) -# TODO: add version number -project(roofer VERSION 1.0.0 LANGUAGES C CXX) - # Global CMake variables are set here We use C++20, with the assumption that we # only implement features that are supported by GCC, Clang, MSVC, Apple Clang set(CMAKE_CXX_STANDARD 20) diff --git a/README-dev.md b/README-dev.md index 9f2d3303..64318109 100644 --- a/README-dev.md +++ b/README-dev.md @@ -123,7 +123,7 @@ conan install . \ --options="&:use_val3dity=False" \ --options="&:build_bindings=True" \ --options="&:build_testing=False" -# The Conan options above are forwarded to CMake by the generated toolchain. +# Conan forwards the package options above to the matching RF_* CMake options. cmake -S . -B build \ -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ diff --git a/conanfile.py b/conanfile.py index 23423af4..a269b619 100644 --- a/conanfile.py +++ b/conanfile.py @@ -80,10 +80,10 @@ def generate(self): deps = CMakeDeps(self) deps.generate() tc = CMakeToolchain(self) - tc.variables["RF_USE_CPM"] = "OFF" - tc.variables["RF_BUILD_APPS"] = "ON" if self.options.build_apps else "OFF" - tc.variables["RF_USE_LOGGER_SPDLOG"] = "ON" if self.options.use_spdlog else "OFF" - tc.variables["RF_USE_VAL3DITY"] = "ON" if self.options.use_val3dity else "OFF" - tc.variables["RF_BUILD_BINDINGS"] = "ON" if self.options.build_bindings else "OFF" - tc.variables["RF_BUILD_TESTING"] = "ON" if self.options.build_testing else "OFF" + tc.variables["RF_USE_CPM"] = False + tc.variables["RF_BUILD_APPS"] = bool(self.options.build_apps) + tc.variables["RF_USE_LOGGER_SPDLOG"] = bool(self.options.use_spdlog) + tc.variables["RF_USE_VAL3DITY"] = bool(self.options.use_val3dity) + tc.variables["RF_BUILD_BINDINGS"] = bool(self.options.build_bindings) + tc.variables["RF_BUILD_TESTING"] = bool(self.options.build_testing) tc.generate() diff --git a/docs/developers.md b/docs/developers.md index 6a216293..d2b17283 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -20,7 +20,7 @@ conan install . \ --options="&:use_val3dity=False" \ --options="&:build_bindings=False" \ --options="&:build_testing=False" -# The Conan options above are forwarded to CMake by the generated toolchain. +# Conan forwards the package options above to the matching RF_* CMake options. cmake -S . -B build \ -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ @@ -80,7 +80,7 @@ conan install . \ --options="&:use_val3dity=False" \ --options="&:build_bindings=True" \ --options="&:build_testing=False" -# The Conan options above are forwarded to CMake by the generated toolchain. +# Conan forwards the package options above to the matching RF_* CMake options. cmake -S . -B build \ -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \ From 1ae4dacfe53aa28f224b39d434e75e8577fd697e Mon Sep 17 00:00:00 2001 From: ylannl Date: Fri, 17 Apr 2026 15:52:06 +0200 Subject: [PATCH 53/53] dont fail on lint error --- .github/workflows/cpp-linter.yml | 3 +-- flake.nix | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml index 0898baee..51792ba4 100644 --- a/.github/workflows/cpp-linter.yml +++ b/.github/workflows/cpp-linter.yml @@ -26,8 +26,7 @@ jobs: files-changed-only: false thread-comments: false - - name: Fail on linter issues + - name: Report linter issues if: steps.linter.outputs.checks-failed != 0 run: | echo "some linter checks failed. ${{ steps.linter.outputs.checks-failed }}" - exit 1 diff --git a/flake.nix b/flake.nix index f32f0efd..bcd7ab3d 100644 --- a/flake.nix +++ b/flake.nix @@ -181,6 +181,7 @@ } { buildInputs = with pkgs; [ cmakeCurses + ninja # roofer core deps cgal @@ -198,6 +199,8 @@ spdlog mimalloc gdal + proj + sqlite nlohmann_json LAStools geos @@ -206,6 +209,10 @@ py uv + # linting + llvmPackages_18.clang + llvmPackages_18.clang-tools + # docs doxygen ] ++ lib.optionals stdenv.isDarwin [ darwin.DarwinTools apple_sdk ];