diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-13 11:04:35 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-13 11:04:35 +0000 |
| commit | 85bd5c6663dc8c663f86828d1b1a5273ee0a82fe (patch) | |
| tree | 1618bd538c148f9164c95405898bba454e2836b3 /cmake | |
| parent | 08fe39b4394b0fcd0272e08f17b7fed852bc2a47 (diff) | |
build: extract package dependencies and prefer local libraries
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat (limited to 'cmake')
| -rw-r--r-- | cmake/EmbeddedLLVM.cmake | 4 | ||||
| -rw-r--r-- | cmake/ExtractPackages.cmake | 86 | ||||
| -rw-r--r-- | cmake/HomebrewPackages.cmake | 101 | ||||
| -rw-r--r-- | cmake/LinuxPackages.cmake | 34 | ||||
| -rw-r--r-- | cmake/LocalDependencies.cmake | 106 | ||||
| -rw-r--r-- | cmake/WindowsPackages.cmake | 14 |
6 files changed, 343 insertions, 2 deletions
diff --git a/cmake/EmbeddedLLVM.cmake b/cmake/EmbeddedLLVM.cmake index 3b56a3c..c7ad5d4 100644 --- a/cmake/EmbeddedLLVM.cmake +++ b/cmake/EmbeddedLLVM.cmake @@ -14,10 +14,10 @@ foreach(cpsc_dependency IN LISTS cpsc_support_libs) continue() endif() list(GET cpsc_library_names 0 cpsc_library_name) - find_library(CPSC_STATIC_${cpsc_library_name} NAMES ${cpsc_library_names}) + cpsc_find_library(CPSC_STATIC_${cpsc_library_name} ${cpsc_library_names}) if(NOT CPSC_STATIC_${cpsc_library_name}) message(FATAL_ERROR - "EMBEDDED requires a static archive for ${cpsc_dependency}; install its development package") + "EMBEDDED requires a static archive for ${cpsc_dependency}; download or install its development package") endif() string(REPLACE "${cpsc_dependency}" "${CPSC_STATIC_${cpsc_library_name}}" cpsc_support_libs "${cpsc_support_libs}") diff --git a/cmake/ExtractPackages.cmake b/cmake/ExtractPackages.cmake new file mode 100644 index 0000000..7b525d7 --- /dev/null +++ b/cmake/ExtractPackages.cmake @@ -0,0 +1,86 @@ +# Use CMake's libarchive support, including on hosts without dpkg or pacman. +function(cpsc_extract_archive archive destination) + file(MAKE_DIRECTORY "${destination}") + execute_process( + COMMAND "${CMAKE_COMMAND}" -E tar xf "${archive}" + WORKING_DIRECTORY "${destination}" + RESULT_VARIABLE result ERROR_VARIABLE error + ) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot extract dependency package ${archive}: ${error}") + endif() +endfunction() + +function(cpsc_extract_packages) + file(GLOB_RECURSE packages CONFIGURE_DEPENDS + "${CPSC_DEPS_DIR}/packages/*.deb" + "${CPSC_DEPS_DIR}/packages/*.pkg.tar.*" + "${CPSC_DEPS_DIR}/packages/*.bottle*.tar.gz" + ) + list(FILTER packages EXCLUDE REGEX "\\.sig$") + # Homebrew exposes both a cache symlink and its downloads/ target. + set(archives) + foreach(package IN LISTS packages) + get_filename_component(package "${package}" REALPATH) + list(APPEND archives "${package}") + endforeach() + list(REMOVE_DUPLICATES archives) + list(SORT archives) + + set(stamp "${CPSC_DEPS_DIR}/packages.sha256") + if(NOT archives) + if(EXISTS "${stamp}") + file(REMOVE_RECURSE "${CPSC_DEPS_ROOT}") + file(REMOVE "${stamp}") + endif() + return() + endif() + + set(signature "${CPSC_DEPS_ROOT}\n") + foreach(archive IN LISTS archives) + file(SHA256 "${archive}" hash) + string(APPEND signature "${archive}:${hash}\n") + endforeach() + foreach(module ExtractPackages LinuxPackages WindowsPackages HomebrewPackages) + file(SHA256 "${CPSC_DEPS_MODULE_DIR}/${module}.cmake" hash) + string(APPEND signature "${module}:${hash}\n") + endforeach() + if(EXISTS "${stamp}" AND IS_DIRECTORY "${CPSC_DEPS_ROOT}") + file(READ "${stamp}" previous_signature) + if(signature STREQUAL previous_signature) + return() + endif() + endif() + + # Rebuild the managed extraction directory when the package set changes. + file(REMOVE "${stamp}") + file(REMOVE_RECURSE "${CPSC_DEPS_ROOT}") + file(MAKE_DIRECTORY "${CPSC_DEPS_ROOT}") + foreach(archive IN LISTS archives) + message(STATUS "Extracting dependency package: ${archive}") + if(archive MATCHES "\\.deb$") + set(unpack "${CPSC_DEPS_DIR}/unpack") + file(REMOVE_RECURSE "${unpack}") + cpsc_extract_archive("${archive}" "${unpack}") + file(GLOB data "${unpack}/data.tar.*" "${unpack}/data.tar") + list(LENGTH data data_count) + if(NOT data_count EQUAL 1) + message(FATAL_ERROR "Dependency package ${archive} must contain one data.tar archive") + endif() + cpsc_extract_archive("${data}" "${CPSC_DEPS_ROOT}") + file(REMOVE_RECURSE "${unpack}") + elseif(archive MATCHES "\\.bottle[^/]*\\.tar\\.gz$") + cpsc_extract_archive("${archive}" "${CPSC_DEPS_ROOT}/Cellar") + else() + cpsc_extract_archive("${archive}" "${CPSC_DEPS_ROOT}") + endif() + endforeach() + if(CMAKE_HOST_APPLE) + cpsc_prepare_homebrew_packages() + elseif(CMAKE_HOST_WIN32) + cpsc_prepare_windows_packages() + else() + cpsc_prepare_linux_packages() + endif() + file(WRITE "${stamp}" "${signature}") +endfunction() diff --git a/cmake/HomebrewPackages.cmake b/cmake/HomebrewPackages.cmake new file mode 100644 index 0000000..edb40f4 --- /dev/null +++ b/cmake/HomebrewPackages.cmake @@ -0,0 +1,101 @@ +function(cpsc_homebrew_path input output) + set(path "${input}") + foreach(prefix "@@HOMEBREW_PREFIX@@" /opt/homebrew /usr/local) + string(REPLACE "${prefix}/" "${CPSC_DEPS_ROOT}/" path "${path}") + endforeach() + string(REPLACE "@@HOMEBREW_CELLAR@@" "${CPSC_DEPS_ROOT}/Cellar" path "${path}") + set(${output} "${path}" PARENT_SCOPE) +endfunction() + +function(cpsc_prepare_homebrew_packages) + file(GLOB kegs LIST_DIRECTORIES TRUE "${CPSC_DEPS_ROOT}/Cellar/*/*") + file(MAKE_DIRECTORY "${CPSC_DEPS_ROOT}/opt") + foreach(keg IN LISTS kegs) + if(NOT IS_DIRECTORY "${keg}") + continue() + endif() + get_filename_component(rack "${keg}" DIRECTORY) + get_filename_component(formula "${rack}" NAME) + set(link "${CPSC_DEPS_ROOT}/opt/${formula}") + if(EXISTS "${link}") + message(FATAL_ERROR "Multiple versions of ${formula} in deps/packages; keep only one") + endif() + execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink "${keg}" "${link}" + RESULT_VARIABLE result) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot create local Homebrew prefix for ${formula}") + endif() + + file(GLOB_RECURSE texts "${keg}/*.cmake" "${keg}/*.pc") + foreach(path IN LISTS texts) + file(READ "${path}" contents) + cpsc_homebrew_path("${contents}" contents) + file(WRITE "${path}" "${contents}") + endforeach() + endforeach() + + # Bottles carry placeholder install names; extraction alone is insufficient. + find_program(cpsc_otool otool) + find_program(cpsc_install_name_tool install_name_tool) + find_program(cpsc_codesign codesign) + find_program(cpsc_brew brew) + if(NOT cpsc_otool OR NOT cpsc_install_name_tool OR NOT cpsc_codesign) + message(FATAL_ERROR "Local Homebrew bottles require otool, install_name_tool and codesign") + endif() + if(cpsc_brew) + execute_process(COMMAND "${cpsc_brew}" --prefix + OUTPUT_VARIABLE system_prefix OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE result) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot determine the system Homebrew prefix") + endif() + endif() + foreach(keg IN LISTS kegs) + file(GLOB binaries "${keg}/bin/*") + file(GLOB_RECURSE libraries "${keg}/lib/*.dylib" "${keg}/lib/*.so") + foreach(binary IN LISTS binaries libraries) + if(IS_SYMLINK "${binary}" OR IS_DIRECTORY "${binary}") + continue() + endif() + execute_process(COMMAND "${cpsc_otool}" -L "${binary}" + OUTPUT_VARIABLE linkage ERROR_QUIET RESULT_VARIABLE result) + if(NOT result EQUAL 0) + continue() # Scripts and data files are not Mach-O binaries. + endif() + string(REGEX MATCHALL "\n[ \t]+[^ \t\n]+ \\(" references "${linkage}") + set(options) + foreach(reference IN LISTS references) + string(REGEX REPLACE "^\n[ \t]+([^ \t]+) \\($" "\\1" old "${reference}") + cpsc_homebrew_path("${old}" new) + if(NOT old STREQUAL new) + if(NOT EXISTS "${new}" AND system_prefix) + string(REPLACE "${CPSC_DEPS_ROOT}/" "${system_prefix}/" system "${new}") + if(EXISTS "${system}") + set(new "${system}") + endif() + endif() + list(APPEND options -change "${old}" "${new}") + endif() + endforeach() + execute_process(COMMAND "${cpsc_otool}" -D "${binary}" + OUTPUT_VARIABLE identity ERROR_QUIET) + string(REGEX MATCH "\n([^\n]+)" match "${identity}") + if(match) + cpsc_homebrew_path("${CMAKE_MATCH_1}" new_identity) + list(APPEND options -id "${new_identity}") + endif() + if(options) + execute_process(COMMAND "${cpsc_install_name_tool}" ${options} "${binary}" + RESULT_VARIABLE result ERROR_VARIABLE error) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot relocate ${binary}: ${error}") + endif() + execute_process(COMMAND "${cpsc_codesign}" --force --sign - "${binary}" + RESULT_VARIABLE result ERROR_VARIABLE error) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot sign relocated ${binary}: ${error}") + endif() + endif() + endforeach() + endforeach() +endfunction() diff --git a/cmake/LinuxPackages.cmake b/cmake/LinuxPackages.cmake new file mode 100644 index 0000000..9e9f26b --- /dev/null +++ b/cmake/LinuxPackages.cmake @@ -0,0 +1,34 @@ +function(cpsc_prepare_linux_packages) + # Development packages can contain absolute links to runtime packages. + file(GLOB_RECURSE files LIST_DIRECTORIES FALSE "${CPSC_DEPS_ROOT}/*") + foreach(path IN LISTS files) + if(IS_SYMLINK "${path}") + execute_process(COMMAND readlink "${path}" OUTPUT_VARIABLE target + OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE result) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot read dependency symlink ${path}") + endif() + if(IS_ABSOLUTE "${target}" AND EXISTS "${CPSC_DEPS_ROOT}${target}") + get_filename_component(directory "${path}" DIRECTORY) + file(RELATIVE_PATH relative "${directory}" "${CPSC_DEPS_ROOT}${target}") + file(REMOVE "${path}") + execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink + "${relative}" "${path}" RESULT_VARIABLE result) + if(NOT result EQUAL 0) + message(FATAL_ERROR "Cannot relocate dependency symlink ${path}") + endif() + endif() + elseif(path MATCHES "\\.cmake$") + # For example, Debian's LLVMSupport interface hard-codes libz3.so. + file(READ "${path}" contents) + string(REGEX MATCHALL "/(usr/)?lib[^\" ;()\n]+" references "${contents}") + foreach(reference IN LISTS references) + if(EXISTS "${CPSC_DEPS_ROOT}${reference}") + string(REPLACE "${reference}" "${CPSC_DEPS_ROOT}${reference}" + contents "${contents}") + endif() + endforeach() + file(WRITE "${path}" "${contents}") + endif() + endforeach() +endfunction() diff --git a/cmake/LocalDependencies.cmake b/cmake/LocalDependencies.cmake new file mode 100644 index 0000000..8160f86 --- /dev/null +++ b/cmake/LocalDependencies.cmake @@ -0,0 +1,106 @@ +set(CPSC_DEPS_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(CPSC_DEPS_DIR "${CMAKE_BINARY_DIR}/deps") +set(CPSC_DEPS_ROOT "${CPSC_DEPS_DIR}/root") +include("${CPSC_DEPS_MODULE_DIR}/ExtractPackages.cmake") +include("${CPSC_DEPS_MODULE_DIR}/LinuxPackages.cmake") +include("${CPSC_DEPS_MODULE_DIR}/WindowsPackages.cmake") +include("${CPSC_DEPS_MODULE_DIR}/HomebrewPackages.cmake") +cpsc_extract_packages() + +set(cpsc_local_prefixes) +foreach(prefix "${CPSC_DEPS_ROOT}/usr" "${CPSC_DEPS_ROOT}/mingw64" + "${CPSC_DEPS_ROOT}/ucrt64" "${CPSC_DEPS_ROOT}/clang64") + if(IS_DIRECTORY "${prefix}") + list(APPEND cpsc_local_prefixes "${prefix}") + endif() +endforeach() +file(GLOB llvm_prefixes LIST_DIRECTORIES TRUE "${CPSC_DEPS_ROOT}/usr/lib/llvm-*") +file(GLOB brew_prefixes LIST_DIRECTORIES TRUE "${CPSC_DEPS_ROOT}/opt/*") +list(APPEND cpsc_local_prefixes ${llvm_prefixes} ${brew_prefixes}) + +set(CPSC_LOCAL_LIBRARY_DIRS) +set(CPSC_LOCAL_INCLUDE_DIRS) +set(CPSC_LOCAL_BIN_DIRS) +foreach(prefix IN LISTS cpsc_local_prefixes) + list(APPEND CPSC_LOCAL_LIBRARY_DIRS "${prefix}/lib") + list(APPEND CPSC_LOCAL_INCLUDE_DIRS "${prefix}/include") + list(APPEND CPSC_LOCAL_INCLUDE_DIRS "${prefix}/include/libxml2") + list(APPEND CPSC_LOCAL_BIN_DIRS "${prefix}/bin") +endforeach() +if(CMAKE_LIBRARY_ARCHITECTURE) + list(INSERT CPSC_LOCAL_LIBRARY_DIRS 0 + "${CPSC_DEPS_ROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}" + "${CPSC_DEPS_ROOT}/lib/${CMAKE_LIBRARY_ARCHITECTURE}") + list(APPEND CPSC_LOCAL_INCLUDE_DIRS + "${CPSC_DEPS_ROOT}/usr/include/${CMAKE_LIBRARY_ARCHITECTURE}") +endif() +if(cpsc_local_prefixes) + list(INSERT CMAKE_PREFIX_PATH 0 ${cpsc_local_prefixes}) +endif() +list(INSERT CMAKE_LIBRARY_PATH 0 ${CPSC_LOCAL_LIBRARY_DIRS}) +list(INSERT CMAKE_INCLUDE_PATH 0 ${CPSC_LOCAL_INCLUDE_DIRS}) + +# Search afresh so cached system paths do not defeat newly downloaded packages. +function(cpsc_find_library output) + unset(cpsc_local_library CACHE) + find_library(cpsc_local_library NAMES ${ARGN} + PATHS ${CPSC_LOCAL_LIBRARY_DIRS} NO_DEFAULT_PATH) + if(cpsc_local_library) + set(selected "${cpsc_local_library}") + else() + unset(cpsc_system_library CACHE) + find_library(cpsc_system_library NAMES ${ARGN}) + set(selected "${cpsc_system_library}") + endif() + set(${output} "${selected}" CACHE FILEPATH "Resolved dependency library" FORCE) + set(${output} "${selected}" PARENT_SCOPE) +endfunction() + +function(cpsc_find_include output name) + unset(cpsc_local_include CACHE) + find_path(cpsc_local_include NAMES "${name}" + PATHS ${CPSC_LOCAL_INCLUDE_DIRS} NO_DEFAULT_PATH) + if(cpsc_local_include) + set(${output} "${cpsc_local_include}" CACHE PATH "Local dependency headers" FORCE) + set(${output} "${cpsc_local_include}" PARENT_SCOPE) + elseif(DEFINED ${output} AND "${${output}}" MATCHES "^${CPSC_DEPS_ROOT}/") + unset(${output} CACHE) + unset(${output} PARENT_SCOPE) + endif() +endfunction() + +unset(cpsc_local_llvm CACHE) +find_path(cpsc_local_llvm LLVMConfig.cmake PATHS ${cpsc_local_prefixes} + PATH_SUFFIXES lib/cmake/llvm cmake NO_DEFAULT_PATH) +if(cpsc_local_llvm) + set(LLVM_DIR "${cpsc_local_llvm}" CACHE PATH "LLVM CMake package directory" FORCE) + set(LLVM_DIR "${cpsc_local_llvm}") +elseif(LLVM_DIR MATCHES "^${CPSC_DEPS_ROOT}/") + unset(LLVM_DIR CACHE) + unset(LLVM_DIR) +endif() +# Config-mode dependencies also need to override earlier system cache entries. +unset(cpsc_local_zstd CACHE) +find_path(cpsc_local_zstd zstdConfig.cmake PATHS ${cpsc_local_prefixes} + PATH_SUFFIXES lib/cmake/zstd "lib/${CMAKE_LIBRARY_ARCHITECTURE}/cmake/zstd" + NO_DEFAULT_PATH) +if(cpsc_local_zstd) + set(zstd_DIR "${cpsc_local_zstd}" CACHE PATH "zstd CMake package directory" FORCE) + set(zstd_DIR "${cpsc_local_zstd}") +elseif(zstd_DIR MATCHES "^${CPSC_DEPS_ROOT}/") + unset(zstd_DIR CACHE) + unset(zstd_DIR) +endif() +cpsc_find_library(ZLIB_LIBRARY_RELEASE z) +cpsc_find_library(zstd_LIBRARY zstd zstd_static) +cpsc_find_library(zstd_STATIC_LIBRARY libzstd.a libzstd_static.a) +cpsc_find_library(Terminfo_LIBRARIES terminfo tinfo curses ncurses ncursesw) +cpsc_find_library(FFI_LIBRARIES ffi) +cpsc_find_library(FFI_STATIC_LIBRARIES libffi.a) +cpsc_find_library(LibEdit_LIBRARIES edit) +cpsc_find_library(LIBXML2_LIBRARY xml2) +cpsc_find_include(ZLIB_INCLUDE_DIR zlib.h) +cpsc_find_include(zstd_INCLUDE_DIR zstd.h) +cpsc_find_include(FFI_INCLUDE_DIRS ffi.h) +cpsc_find_include(LibEdit_INCLUDE_DIRS histedit.h) +cpsc_find_include(LIBXML2_INCLUDE_DIR libxml/xpath.h) diff --git a/cmake/WindowsPackages.cmake b/cmake/WindowsPackages.cmake new file mode 100644 index 0000000..2bd885b --- /dev/null +++ b/cmake/WindowsPackages.cmake @@ -0,0 +1,14 @@ +function(cpsc_prepare_windows_packages) + # Keep the MSYS2 prefix layout so relative CMake imports and DLL paths work. + file(GLOB_RECURSE configs "${CPSC_DEPS_ROOT}/*.cmake") + foreach(config IN LISTS configs) + file(READ "${config}" contents) + foreach(prefix mingw64 ucrt64 clang64) + string(REPLACE "C:/msys64/${prefix}/" "${CPSC_DEPS_ROOT}/${prefix}/" + contents "${contents}") + string(REPLACE "\"/${prefix}/" "\"${CPSC_DEPS_ROOT}/${prefix}/" + contents "${contents}") + endforeach() + file(WRITE "${config}" "${contents}") + endforeach() +endfunction() |
