|
| 1 | +# opts: |
| 2 | +# FFF_VERSION (string) - release tag / git ref. |
| 3 | +# FFF_LIBC (glibc|musl|auto) - only for prebuilt Linux. |
| 4 | +# FFF_BUILD_FROM_SOURCE (BOOL, default OFF) - cargo build fff locally. |
| 5 | +# FFF_CARGO_FEATURES (string, default "") - comma list for --features. |
| 6 | +# FFF_CARGO_PROFILE (release|dev, default release) |
| 7 | +# |
| 8 | +# outputs of ${CMAKE_BINARY_DIR}/_fff: |
| 9 | +# _fff/lib/libfff_c.<ext> (prebuilt mode) |
| 10 | +# _fff/include/fff.h (prebuilt mode) |
| 11 | +# _fff/.stamp-<FFF_VERSION>-<triple> (prebuilt cache invalidator) |
| 12 | +# _fff/src/ (source-build clone) |
| 13 | +# _fff/cargo/<profile>/libfff_c.<ext> (source-build output) |
| 14 | + |
| 15 | +set(FFF_VERSION "v0.8.1" CACHE STRING "fff release tag / git ref to use") |
| 16 | +set(FFF_LIBC "auto" CACHE STRING "Linux C library variant for fff: glibc | musl | auto") |
| 17 | +set_property(CACHE FFF_LIBC PROPERTY STRINGS "auto" "glibc" "musl") |
| 18 | +option(FFF_BUILD_FROM_SOURCE "Build libfff_c locally with cargo (requires Rust toolchain)" OFF) |
| 19 | +set(FFF_CARGO_FEATURES "" CACHE STRING "fff feature flags, provide 'zlob' if you have zig toolchain installed") |
| 20 | +set(FFF_CARGO_PROFILE "release" CACHE STRING "Cargo profile for fff-c (release | dev)") |
| 21 | +set_property(CACHE FFF_CARGO_PROFILE PROPERTY STRINGS "release" "dev") |
| 22 | + |
| 23 | +function(_fff_detect_libc out_libc) |
| 24 | + # probe ldd --version if output |
| 25 | + execute_process( |
| 26 | + COMMAND ldd --version |
| 27 | + OUTPUT_VARIABLE _ldd_out |
| 28 | + ERROR_VARIABLE _ldd_err |
| 29 | + TIMEOUT 5) |
| 30 | + |
| 31 | + if ("${_ldd_out}${_ldd_err}" MATCHES "musl") |
| 32 | + set(${out_libc} "musl" PARENT_SCOPE) |
| 33 | + else() |
| 34 | + set(${out_libc} "glibc" PARENT_SCOPE) |
| 35 | + endif() |
| 36 | +endfunction() |
| 37 | + |
| 38 | +function(_fff_detect_triple out_triple out_ext) |
| 39 | + # Normalize processor |
| 40 | + set(_proc "${CMAKE_SYSTEM_PROCESSOR}") |
| 41 | + if (_proc STREQUAL "AMD64" OR _proc STREQUAL "x64") |
| 42 | + set(_proc "x86_64") |
| 43 | + elseif (_proc STREQUAL "arm64") |
| 44 | + set(_proc "aarch64") |
| 45 | + endif() |
| 46 | + |
| 47 | + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") |
| 48 | + set(_libc "${FFF_LIBC}") |
| 49 | + if (_libc STREQUAL "auto") |
| 50 | + _fff_detect_libc(_libc) |
| 51 | + message(STATUS "fff: auto-detected libc=${_libc}") |
| 52 | + endif() |
| 53 | + |
| 54 | + if (_libc STREQUAL "musl") |
| 55 | + set(${out_triple} "${_proc}-unknown-linux-musl" PARENT_SCOPE) |
| 56 | + else() |
| 57 | + set(${out_triple} "${_proc}-unknown-linux-gnu" PARENT_SCOPE) |
| 58 | + endif() |
| 59 | + set(${out_ext} "so" PARENT_SCOPE) |
| 60 | + elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") # is vicinae even compiled for macos? would be fun lol |
| 61 | + set(${out_triple} "${_proc}-apple-darwin" PARENT_SCOPE) |
| 62 | + set(${out_ext} "dylib" PARENT_SCOPE) |
| 63 | + else() |
| 64 | + message(FATAL_ERROR "fff does not publish a prebuilt C library for ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}. Build from source with -DFFF_BUILD_FROM_SOURCE=ON.") |
| 65 | + endif() |
| 66 | +endfunction() |
| 67 | + |
| 68 | +function(_fff_download url dst) |
| 69 | + message(STATUS "fff: fetching ${url}") |
| 70 | + file(DOWNLOAD "${url}" "${dst}" |
| 71 | + TLS_VERIFY ON |
| 72 | + STATUS _status) |
| 73 | + list(GET _status 0 _rc) |
| 74 | + if (NOT _rc EQUAL 0) |
| 75 | + list(GET _status 1 _err) |
| 76 | + message(FATAL_ERROR "fff: failed to download ${url}: ${_err}") |
| 77 | + endif() |
| 78 | +endfunction() |
| 79 | + |
| 80 | +function(_fff_configure_prebuilt) |
| 81 | + _fff_detect_triple(_triple _ext) |
| 82 | + |
| 83 | + set(_root "${CMAKE_BINARY_DIR}/_fff") |
| 84 | + set(_lib_dir "${_root}/lib") |
| 85 | + set(_inc_dir "${_root}/include") |
| 86 | + set(_libfile "${_lib_dir}/libfff_c.${_ext}") |
| 87 | + set(_hdrfile "${_inc_dir}/fff.h") |
| 88 | + set(_stamp "${_root}/.stamp-${FFF_VERSION}-${_triple}") |
| 89 | + |
| 90 | + if (NOT EXISTS "${_stamp}") |
| 91 | + # Version or triple changed. Wipe any prior prebuilt cache. |
| 92 | + if (EXISTS "${_lib_dir}") |
| 93 | + file(REMOVE_RECURSE "${_lib_dir}") |
| 94 | + endif() |
| 95 | + if (EXISTS "${_inc_dir}") |
| 96 | + file(REMOVE_RECURSE "${_inc_dir}") |
| 97 | + endif() |
| 98 | + file(GLOB _old_stamps "${_root}/.stamp-*") |
| 99 | + if (_old_stamps) |
| 100 | + file(REMOVE ${_old_stamps}) |
| 101 | + endif() |
| 102 | + file(MAKE_DIRECTORY "${_lib_dir}" "${_inc_dir}") |
| 103 | + |
| 104 | + set(_base "https://github.com/dmtrKovalenko/fff/releases/download/${FFF_VERSION}") |
| 105 | + set(_asset "c-lib-${_triple}.${_ext}") |
| 106 | + _fff_download("${_base}/${_asset}" "${_libfile}") |
| 107 | + _fff_download( |
| 108 | + "https://raw.githubusercontent.com/dmtrKovalenko/fff/${FFF_VERSION}/crates/fff-c/include/fff.h" |
| 109 | + "${_hdrfile}") |
| 110 | + |
| 111 | + file(WRITE "${_stamp}" "${FFF_VERSION} ${_triple}\n") |
| 112 | + endif() |
| 113 | + |
| 114 | + add_library(vicinae::fff SHARED IMPORTED GLOBAL) |
| 115 | + set_target_properties(vicinae::fff PROPERTIES |
| 116 | + IMPORTED_LOCATION "${_libfile}" |
| 117 | + IMPORTED_NO_SONAME TRUE |
| 118 | + INTERFACE_INCLUDE_DIRECTORIES "${_inc_dir}") |
| 119 | + |
| 120 | + set(FFF_RUNTIME_LIBRARY "${_libfile}" CACHE INTERNAL "" FORCE) |
| 121 | + message(STATUS "fff: prebuilt ${FFF_VERSION} (${_triple}) at ${_libfile}") |
| 122 | +endfunction() |
| 123 | + |
| 124 | +function(_fff_configure_source) |
| 125 | + find_program(CARGO cargo) |
| 126 | + if (NOT CARGO) |
| 127 | + message(FATAL_ERROR |
| 128 | + "fff: cargo not found but -DFFF_BUILD_FROM_SOURCE=ON was requested.\n" |
| 129 | + "Install the Rust toolchain (https://rustup.rs) or unset " |
| 130 | + "-DFFF_BUILD_FROM_SOURCE to use the prebuilt binary.") |
| 131 | + endif() |
| 132 | + |
| 133 | + # Library extension still comes from the triple detector; we only use the |
| 134 | + # extension part for source builds. |
| 135 | + _fff_detect_triple(_ignore_triple _ext) |
| 136 | + |
| 137 | + include(FetchContent) |
| 138 | + FetchContent_Declare( |
| 139 | + fff_src |
| 140 | + GIT_REPOSITORY https://github.com/dmtrKovalenko/fff.git |
| 141 | + GIT_TAG ${FFF_VERSION} |
| 142 | + GIT_SHALLOW TRUE |
| 143 | + EXCLUDE_FROM_ALL |
| 144 | + SOURCE_DIR "${CMAKE_BINARY_DIR}/_fff/src" |
| 145 | + ) |
| 146 | + |
| 147 | + # do not invoke subdir becuase fff is a rust project without cmake |
| 148 | + FetchContent_GetProperties(fff_src) |
| 149 | + if (NOT fff_src_POPULATED) |
| 150 | + message(STATUS "fff: cloning source tree ${FFF_VERSION}") |
| 151 | + # FetchContent_Populate is deprecated in 3.30+ but still the supported |
| 152 | + # way to populate without add_subdirectory. Quiet the warning. |
| 153 | + if (POLICY CMP0169) |
| 154 | + cmake_policy(PUSH) |
| 155 | + cmake_policy(SET CMP0169 OLD) |
| 156 | + endif() |
| 157 | + FetchContent_Populate(fff_src) |
| 158 | + if (POLICY CMP0169) |
| 159 | + cmake_policy(POP) |
| 160 | + endif() |
| 161 | + endif() |
| 162 | + |
| 163 | + set(_src_dir "${fff_src_SOURCE_DIR}") |
| 164 | + set(_cargo_dir "${CMAKE_BINARY_DIR}/_fff/cargo") |
| 165 | + set(_profile_dir_name "${FFF_CARGO_PROFILE}") |
| 166 | + if (FFF_CARGO_PROFILE STREQUAL "dev") |
| 167 | + # cargo's `dev` profile outputs into `debug/`. |
| 168 | + set(_profile_dir_name "debug") |
| 169 | + endif() |
| 170 | + set(_libfile "${_cargo_dir}/${_profile_dir_name}/libfff_c.${_ext}") |
| 171 | + set(_hdrdir "${_src_dir}/crates/fff-c/include") |
| 172 | + |
| 173 | + set(_cargo_args build -p fff-c |
| 174 | + --manifest-path "${_src_dir}/Cargo.toml" |
| 175 | + --target-dir "${_cargo_dir}") |
| 176 | + |
| 177 | + if (FFF_CARGO_PROFILE STREQUAL "release") |
| 178 | + list(APPEND _cargo_args --release) |
| 179 | + elseif (NOT FFF_CARGO_PROFILE STREQUAL "dev") |
| 180 | + message(FATAL_ERROR "fff: FFF_CARGO_PROFILE must be 'release' or 'dev' (got '${FFF_CARGO_PROFILE}')") |
| 181 | + endif() |
| 182 | + |
| 183 | + if (FFF_CARGO_FEATURES) |
| 184 | + string(REPLACE " " "," _features "${FFF_CARGO_FEATURES}") |
| 185 | + list(APPEND _cargo_args --features "${_features}") |
| 186 | + endif() |
| 187 | + |
| 188 | + # cargo manages it's own compilation, so we invalicate it on version or rust code change |
| 189 | + file(GLOB_RECURSE _fff_src_glob |
| 190 | + CONFIGURE_DEPENDS |
| 191 | + "${_src_dir}/crates/fff-c/src/*.rs" |
| 192 | + "${_src_dir}/crates/fff-c/build.rs" |
| 193 | + "${_src_dir}/crates/fff-c/Cargo.toml") |
| 194 | + |
| 195 | + add_custom_command( |
| 196 | + OUTPUT "${_libfile}" |
| 197 | + COMMAND ${CARGO} ${_cargo_args} |
| 198 | + WORKING_DIRECTORY "${_src_dir}" |
| 199 | + DEPENDS |
| 200 | + "${_src_dir}/Cargo.toml" |
| 201 | + "${_src_dir}/Cargo.lock" |
| 202 | + ${_fff_src_glob} |
| 203 | + COMMENT "fff: cargo build (${FFF_CARGO_PROFILE}, features=${FFF_CARGO_FEATURES})" |
| 204 | + VERBATIM |
| 205 | + USES_TERMINAL) |
| 206 | + |
| 207 | + add_custom_target(fff_c_build ALL DEPENDS "${_libfile}") |
| 208 | + |
| 209 | + add_library(vicinae::fff SHARED IMPORTED GLOBAL) |
| 210 | + set_target_properties(vicinae::fff PROPERTIES |
| 211 | + IMPORTED_LOCATION "${_libfile}" |
| 212 | + IMPORTED_NO_SONAME TRUE |
| 213 | + INTERFACE_INCLUDE_DIRECTORIES "${_hdrdir}") |
| 214 | + add_dependencies(vicinae::fff fff_c_build) |
| 215 | + |
| 216 | + set(FFF_RUNTIME_LIBRARY "${_libfile}" CACHE INTERNAL "" FORCE) |
| 217 | + message(STATUS "fff: building from source (${FFF_VERSION}, profile=${FFF_CARGO_PROFILE}, features=${FFF_CARGO_FEATURES}) -> ${_libfile}") |
| 218 | +endfunction() |
| 219 | + |
| 220 | +function(fff_configure) |
| 221 | + if (TARGET vicinae::fff) |
| 222 | + return() |
| 223 | + endif() |
| 224 | + |
| 225 | + if (FFF_BUILD_FROM_SOURCE) |
| 226 | + _fff_configure_source() |
| 227 | + else() |
| 228 | + _fff_configure_prebuilt() |
| 229 | + endif() |
| 230 | +endfunction() |
0 commit comments