aboutsummaryrefslogtreecommitdiff
path: root/cmake
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 11:04:35 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 11:04:35 +0000
commit85bd5c6663dc8c663f86828d1b1a5273ee0a82fe (patch)
tree1618bd538c148f9164c95405898bba454e2836b3 /cmake
parent08fe39b4394b0fcd0272e08f17b7fed852bc2a47 (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.cmake4
-rw-r--r--cmake/ExtractPackages.cmake86
-rw-r--r--cmake/HomebrewPackages.cmake101
-rw-r--r--cmake/LinuxPackages.cmake34
-rw-r--r--cmake/LocalDependencies.cmake106
-rw-r--r--cmake/WindowsPackages.cmake14
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()