From 66b073ab8d427d8fc06ffd715151f372cdde818c Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 6 Jul 2026 23:11:39 +0200 Subject: [PATCH] [cmake] CPMUtil rewrite of the day number 852 (#4130) Composite PR for several changes to CPMUtil, most notably: - https://git.crueter.xyz/CMake/CPMUtil/pulls/19 - https://git.crueter.xyz/CMake/CPMUtil/pulls/22 - https://git.crueter.xyz/CMake/CPMUtil/pulls/24 - https://git.crueter.xyz/CMake/CPMUtil/pulls/25 These contain a lot of changes that generally simplify control flow and improve ease-of-use. Read those descriptions and patchsets for more info. Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4130 Reviewed-by: CamilleLaVey Reviewed-by: MaranBr --- .patch/libadrenotools/0001-linkerns-cpm.patch | 20 - ...1-use-cpmfile-def-for-linkernsbypass.patch | 26 + CMakeLists.txt | 2 +- CMakeModules/CPM.cmake | 1310 ++--------------- CMakeModules/CPMUtil.cmake | 855 ++++++++--- cpmfile.json | 124 +- docs/CPMUtil.md | 154 +- tools/cpm/cmake/README.md | 3 + tools/cpm/cmake/ScriptUtils.cmake | 341 +++++ tools/cpm/{format.sh => cmake/format.cmake} | 8 +- tools/cpm/cmake/ls.cmake | 24 + tools/cpm/cmake/package/add.cmake | 104 ++ tools/cpm/cmake/package/dir.cmake | 33 + tools/cpm/cmake/package/fetch.cmake | 34 + tools/cpm/cmake/package/hash.cmake | 59 + tools/cpm/cmake/package/patch.cmake | 155 ++ tools/cpm/cmake/package/reset.cmake | 34 + tools/cpm/cmake/package/rm.cmake | 31 + tools/cpm/cmake/package/update.cmake | 97 ++ tools/cpm/cmake/package/url.cmake | 28 + tools/cpm/cmake/package/version.cmake | 75 + tools/cpm/cmake/package/which.cmake | 36 + tools/cpm/cmake/update.cmake | 33 + tools/cpm/common.sh | 45 - tools/cpm/migrate.sh | 39 +- tools/cpm/package.sh | 63 +- tools/cpm/package/add.sh | 151 +- tools/cpm/package/dir.sh | 57 - tools/cpm/package/download.sh | 47 - tools/cpm/package/fetch.sh | 44 - tools/cpm/package/hash.sh | 91 -- tools/cpm/package/patch.sh | 169 +-- tools/cpm/package/reset.sh | 56 - tools/cpm/package/rm.sh | 30 - tools/cpm/package/update.sh | 174 --- tools/cpm/package/util/fetch.sh | 134 -- tools/cpm/package/util/fix-hash.sh | 28 - tools/cpm/package/util/interactive.sh | 228 --- tools/cpm/package/util/replace.sh | 11 - tools/cpm/package/util/url-hash.sh | 7 - tools/cpm/package/util/url.sh | 27 - tools/cpm/package/vars.sh | 158 -- tools/cpm/package/version.sh | 62 - tools/cpm/package/which.sh | 8 - tools/cpmutil.sh | 53 +- 45 files changed, 2291 insertions(+), 2977 deletions(-) delete mode 100644 .patch/libadrenotools/0001-linkerns-cpm.patch create mode 100644 .patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch create mode 100755 tools/cpm/cmake/README.md create mode 100755 tools/cpm/cmake/ScriptUtils.cmake rename tools/cpm/{format.sh => cmake/format.cmake} (53%) create mode 100755 tools/cpm/cmake/ls.cmake create mode 100644 tools/cpm/cmake/package/add.cmake create mode 100755 tools/cpm/cmake/package/dir.cmake create mode 100755 tools/cpm/cmake/package/fetch.cmake create mode 100755 tools/cpm/cmake/package/hash.cmake create mode 100644 tools/cpm/cmake/package/patch.cmake create mode 100755 tools/cpm/cmake/package/reset.cmake create mode 100755 tools/cpm/cmake/package/rm.cmake create mode 100644 tools/cpm/cmake/package/update.cmake create mode 100755 tools/cpm/cmake/package/url.cmake create mode 100755 tools/cpm/cmake/package/version.cmake create mode 100755 tools/cpm/cmake/package/which.cmake create mode 100755 tools/cpm/cmake/update.cmake delete mode 100755 tools/cpm/common.sh delete mode 100755 tools/cpm/package/dir.sh delete mode 100755 tools/cpm/package/download.sh delete mode 100755 tools/cpm/package/fetch.sh delete mode 100755 tools/cpm/package/hash.sh delete mode 100755 tools/cpm/package/reset.sh delete mode 100755 tools/cpm/package/rm.sh delete mode 100755 tools/cpm/package/update.sh delete mode 100755 tools/cpm/package/util/fetch.sh delete mode 100755 tools/cpm/package/util/fix-hash.sh delete mode 100755 tools/cpm/package/util/interactive.sh delete mode 100755 tools/cpm/package/util/replace.sh delete mode 100755 tools/cpm/package/util/url-hash.sh delete mode 100755 tools/cpm/package/util/url.sh delete mode 100755 tools/cpm/package/vars.sh delete mode 100755 tools/cpm/package/version.sh delete mode 100755 tools/cpm/package/which.sh diff --git a/.patch/libadrenotools/0001-linkerns-cpm.patch b/.patch/libadrenotools/0001-linkerns-cpm.patch deleted file mode 100644 index 8c5abe28e5..0000000000 --- a/.patch/libadrenotools/0001-linkerns-cpm.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 16c6092..9e75548 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -8,7 +8,14 @@ project(adrenotools LANGUAGES CXX C) - - set(GEN_INSTALL_TARGET OFF CACHE BOOL "") - --add_subdirectory(lib/linkernsbypass) -+include(CPM) -+set(CPM_USE_LOCAL_PACKAGES OFF) -+ -+CPMAddPackage( -+ NAME linkernsbypass -+ URL "https://github.com/bylaws/liblinkernsbypass/archive/aa3975893d.zip" -+ URL_HASH SHA512=43d3d146facb7ec99d066a9b8990369ab7b9eec0d5f9a67131b0a0744fde0af27d884ca1f2a272cd113718a23356530ed97703c8c0659c4c25948d50c106119e -+) - - set(LIB_SOURCES src/bcenabler.cpp - src/driver.cpp diff --git a/.patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch b/.patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch new file mode 100644 index 0000000000..965975cda5 --- /dev/null +++ b/.patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch @@ -0,0 +1,26 @@ +From 52bbc5af6523daa22ad62fe4b84bc8d623d11a53 Mon Sep 17 00:00:00 2001 +From: crueter +Date: Fri, 26 Jun 2026 01:09:40 -0400 +Subject: [PATCH] use cpmfile def for linkernsbypass + +--- + CMakeLists.txt | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 16c6092..85b242c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -8,7 +8,8 @@ project(adrenotools LANGUAGES CXX C) + + set(GEN_INSTALL_TARGET OFF CACHE BOOL "") + +-add_subdirectory(lib/linkernsbypass) ++include(CPMUtil) ++AddJsonPackage(linkernsbypass) + + set(LIB_SOURCES src/bcenabler.cpp + src/driver.cpp +-- +2.54.0 + diff --git a/CMakeLists.txt b/CMakeLists.txt index 00a3b39281..d26063b8b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,7 +270,7 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) set(abi ${CMAKE_ANDROID_ARCH_ABI}) set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/${abi}/") - file(COPY "${VVL_SOURCE_DIR}/${abi}/libVkLayer_khronos_validation.so" + file(COPY "${vulkan-validation-layers_SOURCE_DIR}/${abi}/libVkLayer_khronos_validation.so" DESTINATION "${vvl_lib_path}") endif() diff --git a/CMakeModules/CPM.cmake b/CMakeModules/CPM.cmake index 5544d8eefe..431766b579 100644 --- a/CMakeModules/CPM.cmake +++ b/CMakeModules/CPM.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + # CPM.cmake - CMake's missing package manager # =========================================== # See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. @@ -26,65 +29,19 @@ SOFTWARE. ]] -cmake_minimum_required(VERSION 3.14 FATAL_ERROR) +cmake_minimum_required(VERSION 3.31 FATAL_ERROR) # Initialize logging prefix if(NOT CPM_INDENT) - set(CPM_INDENT - "CPM:" - CACHE INTERNAL "" - ) + set(CPM_INDENT "CPM:" CACHE INTERNAL "") endif() -if(NOT COMMAND cpm_message) - function(cpm_message) - message(${ARGV}) - endfunction() +get_property(CPM_INITIALIZED GLOBAL PROPERTY CPM_INITIALIZED) +if(CPM_INITIALIZED) + return() endif() -if(DEFINED EXTRACTED_CPM_VERSION) - set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}") -else() - set(CURRENT_CPM_VERSION 0.42.0) -endif() - -get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) -if(CPM_DIRECTORY) - if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) - if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) - message( - AUTHOR_WARNING - "${CPM_INDENT} \ -A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ -It is recommended to upgrade CPM to the most recent version. \ -See https://github.com/cpm-cmake/CPM.cmake for more information." - ) - endif() - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - include(FetchContent) - endif() - return() - endif() - - get_property( - CPM_INITIALIZED GLOBAL "" - PROPERTY CPM_INITIALIZED - SET - ) - if(CPM_INITIALIZED) - return() - endif() -endif() - -if(CURRENT_CPM_VERSION MATCHES "development-version") - message( - WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \ -Please update to a recent release if possible. \ -See https://github.com/cpm-cmake/CPM.cmake for details." - ) -endif() - -set_property(GLOBAL PROPERTY CPM_INITIALIZED true) +set_property(GLOBAL PROPERTY CPM_INITIALIZED TRUE) macro(cpm_set_policies) # the policy allows us to change options without caching @@ -92,85 +49,40 @@ macro(cpm_set_policies) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # the policy allows us to change set(CACHE) without caching - if(POLICY CMP0126) - cmake_policy(SET CMP0126 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) - endif() + cmake_policy(SET CMP0126 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) - # The policy uses the download time for timestamp, instead of the timestamp in the archive. This + # The policy uses the download time for timestamp, + # instead of the timestamp in the archive. This # allows for proper rebuilds when a projects url changes - if(POLICY CMP0135) - cmake_policy(SET CMP0135 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) - endif() + cmake_policy(SET CMP0135 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) - # treat relative git repository paths as being relative to the parent project's remote - if(POLICY CMP0150) - cmake_policy(SET CMP0150 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) - endif() + # treat relative git repository paths as + # being relative to the parent project's remote + cmake_policy(SET CMP0150 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) + + # Disable sub-build nonsense + cmake_policy(SET CMP0168 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0168 NEW) endmacro() + cpm_set_policies() -option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" - $ENV{CPM_USE_LOCAL_PACKAGES} -) -option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" - $ENV{CPM_LOCAL_PACKAGES_ONLY} -) -option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) -option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" - $ENV{CPM_DONT_UPDATE_MODULE_PATH} -) -option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" - $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} -) -option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK - "Add all packages added through CPM.cmake to the package lock" - $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} -) -option(CPM_USE_NAMED_CACHE_DIRECTORIES - "Use additional directory of package name in cache on the most nested level." - $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} -) +option(CPM_DONT_UPDATE_MODULE_PATH + "Don't update the module path to allow using find_package" + $ENV{CPM_DONT_UPDATE_MODULE_PATH}) -set(CPM_VERSION - ${CURRENT_CPM_VERSION} - CACHE INTERNAL "" -) -set(CPM_DIRECTORY - ${CPM_CURRENT_DIRECTORY} - CACHE INTERNAL "" -) set(CPM_FILE ${CMAKE_CURRENT_LIST_FILE} - CACHE INTERNAL "" -) -set(CPM_PACKAGES - "" - CACHE INTERNAL "" -) -set(CPM_DRY_RUN - OFF - CACHE INTERNAL "Don't download or configure dependencies (for testing)" -) - -if(DEFINED ENV{CPM_SOURCE_CACHE}) - set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) -else() - set(CPM_SOURCE_CACHE_DEFAULT OFF) -endif() + CACHE INTERNAL "") -set(CPM_SOURCE_CACHE - ${CPM_SOURCE_CACHE_DEFAULT} - CACHE PATH "Directory to download CPM dependencies" -) +set(CPM_PACKAGES "" CACHE INTERNAL "") -if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) - set(CPM_MODULE_PATH - "${CMAKE_BINARY_DIR}/CPM_modules" - CACHE INTERNAL "" - ) +if(NOT CPM_DONT_UPDATE_MODULE_PATH + AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) + set(CPM_MODULE_PATH "${CMAKE_BINARY_DIR}/CPM_modules" CACHE INTERNAL "") # remove old modules file(REMOVE_RECURSE ${CPM_MODULE_PATH}) file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) @@ -178,602 +90,105 @@ if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_ set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") endif() -if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - set(CPM_PACKAGE_LOCK_FILE - "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" - CACHE INTERNAL "" - ) - file(WRITE ${CPM_PACKAGE_LOCK_FILE} - "# CPM Package Lock\n# This file should be committed to version control\n\n" - ) -endif() - include(FetchContent) -# Try to infer package name from git repository uri (path or url) -function(cpm_package_name_from_git_uri URI RESULT) - if("${URI}" MATCHES "([^/:]+)/?.git/?$") - set(${RESULT} - ${CMAKE_MATCH_1} - PARENT_SCOPE - ) - else() - unset(${RESULT} PARENT_SCOPE) - endif() -endfunction() - -# Find the shortest hash that can be used eg, if origin_hash is -# cccb77ae9609d2768ed80dd42cec54f77b1f1455 the following files will be checked, until one is found -# that is either empty (allowing us to assign origin_hash), or whose contents matches ${origin_hash} -# -# * .../cccb.hash -# * .../cccb77ae.hash -# * .../cccb77ae9609.hash -# * .../cccb77ae9609d276.hash -# * etc -# -# We will be able to use a shorter path with very high probability, but in the (rare) event that the -# first couple characters collide, we will check longer and longer substrings. -function(cpm_get_shortest_hash source_cache_dir origin_hash short_hash_output_var) - # for compatibility with caches populated by a previous version of CPM, check if a directory using - # the full hash already exists - if(EXISTS "${source_cache_dir}/${origin_hash}") - set(${short_hash_output_var} - "${origin_hash}" - PARENT_SCOPE - ) - return() - endif() - - foreach(len RANGE 4 40 4) - string(SUBSTRING "${origin_hash}" 0 ${len} short_hash) - set(hash_lock ${source_cache_dir}/${short_hash}.lock) - set(hash_fp ${source_cache_dir}/${short_hash}.hash) - # Take a lock, so we don't have a race condition with another instance of cmake. We will release - # this lock when we can, however, if there is an error, we want to ensure it gets released on - # it's own on exit from the function. - file(LOCK ${hash_lock} GUARD FUNCTION) - - # Load the contents of .../${short_hash}.hash - file(TOUCH ${hash_fp}) - file(READ ${hash_fp} hash_fp_contents) - - if(hash_fp_contents STREQUAL "") - # Write the origin hash - file(WRITE ${hash_fp} ${origin_hash}) - file(LOCK ${hash_lock} RELEASE) - break() - elseif(hash_fp_contents STREQUAL origin_hash) - file(LOCK ${hash_lock} RELEASE) - break() - else() - file(LOCK ${hash_lock} RELEASE) - endif() - endforeach() - set(${short_hash_output_var} - "${short_hash}" - PARENT_SCOPE - ) -endfunction() - -# Try to infer package name and version from a url -function(cpm_package_name_and_ver_from_url url outName outVer) - if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") - # We matched an archive - set(filename "${CMAKE_MATCH_1}") - - if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") - # We matched - (ie foo-1.2.3) - set(${outName} - "${CMAKE_MATCH_1}" - PARENT_SCOPE - ) - set(${outVer} - "${CMAKE_MATCH_2}" - PARENT_SCOPE - ) - elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") - # We couldn't find a name, but we found a version - # - # In many cases (which we don't handle here) the url would look something like - # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly - # distinguish the package name from the irrelevant bits. Moreover if we try to match the - # package name from the filename, we'd get bogus at best. - unset(${outName} PARENT_SCOPE) - set(${outVer} - "${CMAKE_MATCH_1}" - PARENT_SCOPE - ) - else() - # Boldly assume that the file name is the package name. - # - # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but - # such cases should be quite rare. No popular service does this... we think. - set(${outName} - "${filename}" - PARENT_SCOPE - ) - unset(${outVer} PARENT_SCOPE) - endif() - else() - # No ideas yet what to do with non-archives - unset(${outName} PARENT_SCOPE) - unset(${outVer} PARENT_SCOPE) - endif() -endfunction() - -function(cpm_find_package NAME VERSION) - string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") - find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) - if(${CPM_ARGS_NAME}_FOUND) - if(DEFINED ${CPM_ARGS_NAME}_VERSION) - set(VERSION ${${CPM_ARGS_NAME}_VERSION}) - endif() - cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}") - CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") - set(CPM_PACKAGE_FOUND - YES - PARENT_SCOPE - ) - else() - set(CPM_PACKAGE_FOUND - NO - PARENT_SCOPE - ) - endif() -endfunction() - -# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from -# finding the system library +# Create a custom FindXXX.cmake module for a CPM package. +# This prevents `find_package(NAME)` from finding the system library function(cpm_create_module_file Name) if(NOT CPM_DONT_UPDATE_MODULE_PATH) if(DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) - # Redirect find_package calls to the CPM package. This is what FetchContent does when you set - # OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for find_package in CONFIG - # mode, unlike the Find${Name}.cmake fallback. CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined - # in script mode, or in CMake < 3.24. - # https://cmake.org/cmake/help/latest/module/FetchContent.html#fetchcontent-find-package-integration-examples + # Redirect find_package calls to the CPM package. + # This is what FetchContent does when you set + # OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for + # find_package in CONFIG mode, unlike the Find${Name}.cmake fallback. + # CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined in script mode + # https://cmake.org/cmake/help/latest/module/FetchContent.html string(TOLOWER ${Name} NameLower) file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config.cmake - "include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" OPTIONAL)\n" - "include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n" - ) - file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake - "set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" "set(PACKAGE_VERSION_EXACT TRUE)\n" - ) + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" \ + OPTIONAL)\n" + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n") + + file(WRITE + ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake + "set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" + "set(PACKAGE_VERSION_EXACT TRUE)\n") else() file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake - "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" - ) + "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)") endif() endif() endfunction() -# Find a package locally or fallback to CPMAddPackage -function(CPMFindPackage) - set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) - - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) - - if(NOT DEFINED CPM_ARGS_VERSION) - if(DEFINED CPM_ARGS_GIT_TAG) - cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) - endif() - endif() - - set(downloadPackage ${CPM_DOWNLOAD_ALL}) - if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) - set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - endif() - if(downloadPackage) - CPMAddPackage(${ARGN}) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) - - if(NOT CPM_PACKAGE_FOUND) - CPMAddPackage(${ARGN}) - cpm_export_variables(${CPM_ARGS_NAME}) - endif() - -endfunction() - # checks if a package has been added before function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") - message( - WARNING - "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." - ) + message(WARNING + "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} " + "(${CPM_ARGS_VERSION}) than currently included " + "(${CPM_PACKAGE_VERSION}).") endif() cpm_get_fetch_properties(${CPM_ARGS_NAME}) set(${CPM_ARGS_NAME}_ADDED NO) - set(CPM_PACKAGE_ALREADY_ADDED - YES - PARENT_SCOPE - ) + set(CPM_PACKAGE_ALREADY_ADDED YES PARENT_SCOPE) cpm_export_variables(${CPM_ARGS_NAME}) else() - set(CPM_PACKAGE_ALREADY_ADDED - NO - PARENT_SCOPE - ) - endif() -endfunction() - -# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of -# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted -# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 -function(cpm_parse_add_package_single_arg arg outArgs) - # Look for a scheme - if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") - string(TOLOWER "${CMAKE_MATCH_1}" scheme) - set(uri "${CMAKE_MATCH_2}") - - # Check for CPM-specific schemes - if(scheme STREQUAL "gh") - set(out "GITHUB_REPOSITORY;${uri}") - set(packageType "git") - elseif(scheme STREQUAL "gl") - set(out "GITLAB_REPOSITORY;${uri}") - set(packageType "git") - elseif(scheme STREQUAL "bb") - set(out "BITBUCKET_REPOSITORY;${uri}") - set(packageType "git") - # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine - # type - elseif(arg MATCHES ".git/?(@|#|$)") - set(out "GIT_REPOSITORY;${arg}") - set(packageType "git") - else() - # Fall back to a URL - set(out "URL;${arg}") - set(packageType "archive") - - # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. - # We just won't bother with the additional complexity it will induce in this function. SVN is - # done by multi-arg - endif() - else() - if(arg MATCHES ".git/?(@|#|$)") - set(out "GIT_REPOSITORY;${arg}") - set(packageType "git") - else() - # Give up - message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'") - endif() - endif() - - # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs - # containing '@' can be used - string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") - - # Parse the rest according to package type - if(packageType STREQUAL "git") - # For git repos we interpret #... as a tag or branch or commit hash - string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") - elseif(packageType STREQUAL "archive") - # For archives we interpret #... as a URL hash. - string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") - # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url - # should do this at a later point - else() - # We should never get here. This is an assertion and hitting it means there's a problem with the - # code above. A packageType was set, but not handled by this if-else. - message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") - endif() - - set(${outArgs} - ${out} - PARENT_SCOPE - ) -endfunction() - -# Check that the working directory for a git repo is clean -function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) - - find_package(Git REQUIRED) - - if(NOT GIT_EXECUTABLE) - # No git executable, assume directory is clean - set(${isClean} - TRUE - PARENT_SCOPE - ) - return() - endif() - - # check for uncommitted changes - execute_process( - COMMAND ${GIT_EXECUTABLE} status --porcelain - RESULT_VARIABLE resultGitStatus - OUTPUT_VARIABLE repoStatus - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET - WORKING_DIRECTORY ${repoPath} - ) - if(resultGitStatus) - # not supposed to happen, assume clean anyway - message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed") - set(${isClean} - TRUE - PARENT_SCOPE - ) - return() - endif() - - if(NOT "${repoStatus}" STREQUAL "") - set(${isClean} - FALSE - PARENT_SCOPE - ) - return() - endif() - - # check for committed changes - execute_process( - COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} - RESULT_VARIABLE resultGitDiff - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET - WORKING_DIRECTORY ${repoPath} - ) - - if(${resultGitDiff} EQUAL 0) - set(${isClean} - TRUE - PARENT_SCOPE - ) - else() - set(${isClean} - FALSE - PARENT_SCOPE - ) + set(CPM_PACKAGE_ALREADY_ADDED NO PARENT_SCOPE) endif() - endfunction() -# Add PATCH_COMMAND to CPM_ARGS_UNPARSED_ARGUMENTS. This method consumes a list of files in ARGN -# then generates a `PATCH_COMMAND` appropriate for `ExternalProject_Add()`. This command is appended -# to the parent scope's `CPM_ARGS_UNPARSED_ARGUMENTS`. -function(cpm_add_patches) - # Return if no patch files are supplied. - if(NOT ARGN) - return() - endif() - - # Find the patch program. - find_program(PATCH_EXECUTABLE patch) - if(CMAKE_HOST_WIN32 AND NOT PATCH_EXECUTABLE) - # The Windows git executable is distributed with patch.exe. Find the path to the executable, if - # it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe. - find_package(Git QUIET) - if(GIT_EXECUTABLE) - get_filename_component(extra_search_path ${GIT_EXECUTABLE} DIRECTORY) - get_filename_component(extra_search_path_1up ${extra_search_path} DIRECTORY) - get_filename_component(extra_search_path_2up ${extra_search_path_1up} DIRECTORY) - find_program( - PATCH_EXECUTABLE patch HINTS "${extra_search_path_1up}/usr/bin" - "${extra_search_path_2up}/usr/bin" - ) - endif() - endif() - if(NOT PATCH_EXECUTABLE) - message(FATAL_ERROR "Couldn't find `patch` executable to use with PATCHES keyword.") - endif() - - # Create a temporary - set(temp_list ${CPM_ARGS_UNPARSED_ARGUMENTS}) - - # Ensure each file exists (or error out) and add it to the list. - set(first_item True) - foreach(PATCH_FILE ${ARGN}) - # Make sure the patch file exists, if we can't find it, try again in the current directory. - if(NOT EXISTS "${PATCH_FILE}") - if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") - message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'") - endif() - set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") - endif() - - # Convert to absolute path for use with patch file command. - get_filename_component(PATCH_FILE "${PATCH_FILE}" ABSOLUTE) - - # The first patch entry must be preceded by "PATCH_COMMAND" while the following items are - # preceded by "&&". - if(first_item) - set(first_item False) - list(APPEND temp_list "PATCH_COMMAND") - else() - list(APPEND temp_list "&&") - endif() - # Add the patch command to the list - list(APPEND temp_list "${PATCH_EXECUTABLE}" "-p1" "<" "${PATCH_FILE}") - endforeach() - - # Move temp out into parent scope. - set(CPM_ARGS_UNPARSED_ARGUMENTS - ${temp_list} - PARENT_SCOPE - ) - -endfunction() - -# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload -# FetchContent calls. As these are internal cmake properties, this method should be used carefully -# and may need modification in future CMake versions. Source: -# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 -function(cpm_override_fetchcontent contentName) - cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") - if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") - message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") - endif() - - string(TOLOWER ${contentName} contentNameLower) - set(prefix "_FetchContent_${contentNameLower}") - - set(propertyName "${prefix}_sourceDir") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") - - set(propertyName "${prefix}_binaryDir") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") - - set(propertyName "${prefix}_populated") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} TRUE) -endfunction() - -# Download and add a package from source function(CPMAddPackage) - cpm_set_policies() - set(oneValueArgs NAME FORCE VERSION - GIT_TAG DOWNLOAD_ONLY - GITHUB_REPOSITORY - GITLAB_REPOSITORY - BITBUCKET_REPOSITORY - GIT_REPOSITORY SOURCE_DIR - FIND_PACKAGE_ARGUMENTS - NO_CACHE SYSTEM - GIT_SHALLOW EXCLUDE_FROM_ALL - SOURCE_SUBDIR - CUSTOM_CACHE_KEY - ) - - set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES) + SOURCE_SUBDIR) - list(LENGTH ARGN argnLength) + set(multiValueArgs OPTIONS) - # Parse single shorthand argument - if(argnLength EQUAL 1) - cpm_parse_add_package_single_arg("${ARGN}" ARGN) - - # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM - set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") - - # Parse URI shorthand argument - elseif(argnLength GREATER 1 AND "${ARGV0}" STREQUAL "URI") - list(REMOVE_AT ARGN 0 1) # remove "URI gh:<...>@version#tag" - cpm_parse_add_package_single_arg("${ARGV1}" ARGV0) - - set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}") - endif() - - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") + cmake_parse_arguments(CPM_ARGS + "" + "${oneValueArgs}" + "${multiValueArgs}" + "${ARGN}") - # Set default values for arguments - if(NOT DEFINED CPM_ARGS_VERSION) - if(DEFINED CPM_ARGS_GIT_TAG) - cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) - endif() + if (NOT DEFINED CPM_ARGS_SOURCE_DIR) + message(FATAL_ERROR "${CPM_INDENT} SOURCE_DIR is required") endif() if(CPM_ARGS_DOWNLOAD_ONLY) set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) else() - set(DOWNLOAD_ONLY NO) + # Default to DOWNLOAD_ONLY in script mode. + set(DOWNLOAD_ONLY ${CMAKE_SCRIPT_MODE_FILE}) endif() - if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") - elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") - elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") - endif() - - if(DEFINED CPM_ARGS_GIT_REPOSITORY) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) - if(NOT DEFINED CPM_ARGS_GIT_TAG) - set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) - endif() - - # If a name wasn't provided, try to infer it from the git repo - if(NOT DEFINED CPM_ARGS_NAME) - cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) - endif() - endif() - - set(CPM_SKIP_FETCH FALSE) - - if(DEFINED CPM_ARGS_GIT_TAG) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) - # If GIT_SHALLOW is explicitly specified, honor the value. - if(DEFINED CPM_ARGS_GIT_SHALLOW) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) - endif() - endif() - - if(DEFINED CPM_ARGS_URL) - # If a name or version aren't provided, try to infer them from the URL - list(GET CPM_ARGS_URL 0 firstUrl) - cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) - # If we fail to obtain name and version from the first URL, we could try other URLs if any. - # However multiple URLs are expected to be quite rare, so for now we won't bother. - - # If the caller provided their own name and version, they trump the inferred ones. - if(NOT DEFINED CPM_ARGS_NAME) - set(CPM_ARGS_NAME ${nameFromUrl}) - endif() - - # this is dumb and should not be done - # if(NOT DEFINED CPM_ARGS_VERSION) - # set(CPM_ARGS_VERSION ${verFromUrl}) - # endif() - - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") + if(NOT DEFINED CPM_ARGS_NAME) + message(FATAL_ERROR "${CPM_INDENT} NAME is required") endif() - # Check for required arguments - - if(NOT DEFINED CPM_ARGS_NAME) - message( - FATAL_ERROR - "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" - ) + if(NOT DEFINED CPM_ARGS_VERSION) + message(FATAL_ERROR "${CPM_INDENT} VERSION is required") endif() - # Check if package has been added before cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") if(CPM_PACKAGE_ALREADY_ADDED) cpm_export_variables(${CPM_ARGS_NAME}) return() endif() - # Check for manual overrides if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) set(CPM_${CPM_ARGS_NAME}_SOURCE "") CPMAddPackage( NAME "${CPM_ARGS_NAME}" + VERSION "${CPM_ARGS_VERSION}" SOURCE_DIR "${PACKAGE_SOURCE}" EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" SYSTEM "${CPM_ARGS_SYSTEM}" @@ -781,455 +196,116 @@ function(CPMAddPackage) OPTIONS "${CPM_ARGS_OPTIONS}" SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" - FORCE True - ) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() + FORCE True) - # Check for available declaration - if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") - set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) - set(CPM_DECLARATION_${CPM_ARGS_NAME} "") - CPMAddPackage(${declaration}) cpm_export_variables(${CPM_ARGS_NAME}) - # checking again to ensure version and option compatibility - cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") return() endif() - if(NOT CPM_ARGS_FORCE) - if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) - - if(CPM_PACKAGE_FOUND) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - if(CPM_LOCAL_PACKAGES_ONLY) - message( - SEND_ERROR - "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" - ) - endif() - endif() - endif() - CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") - if(DEFINED CPM_ARGS_GIT_TAG) - set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") - elseif(DEFINED CPM_ARGS_SOURCE_DIR) - set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") - else() - set(PACKAGE_INFO "${CPM_ARGS_VERSION}") - endif() - if(DEFINED FETCHCONTENT_BASE_DIR) - # respect user's FETCHCONTENT_BASE_DIR if set set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) else() set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) endif() - cpm_add_patches(${CPM_ARGS_PATCHES}) + # Subbuild, etc + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) - elseif(DEFINED CPM_ARGS_SOURCE_DIR) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) - if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) - # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work - # for relative paths. - get_filename_component( - source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} - ) - else() - set(source_directory ${CPM_ARGS_SOURCE_DIR}) - endif() - if(NOT EXISTS ${source_directory}) - string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - # remove timestamps so CMake will re-download the dependency - file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") - endif() - elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) - string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) - list(SORT origin_parameters) - if(CPM_ARGS_CUSTOM_CACHE_KEY) - # Application set a custom unique directory name - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY}) - elseif(CPM_USE_NAMED_CACHE_DIRECTORIES) - string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") - cpm_get_shortest_hash( - "${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory - "${origin_hash}" # Input hash - origin_hash # Computed hash - ) - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) - else() - string(SHA1 origin_hash "${origin_parameters}") - cpm_get_shortest_hash( - "${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory - "${origin_hash}" # Input hash - origin_hash # Computed hash - ) - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) - endif() - # Expand `download_directory` relative path. This is important because EXISTS doesn't work for - # relative paths. - get_filename_component(download_directory ${download_directory} ABSOLUTE) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) - - if(CPM_SOURCE_CACHE) - file(LOCK ${download_directory}/../cmake.lock) - endif() - - if(EXISTS ${download_directory}) - if(CPM_SOURCE_CACHE) - file(LOCK ${download_directory}/../cmake.lock RELEASE) - endif() - - cpm_store_fetch_properties( - ${CPM_ARGS_NAME} "${download_directory}" - "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" - ) - cpm_get_fetch_properties("${CPM_ARGS_NAME}") - - if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) - # warn if cache has been changed since checkout - cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) - if(NOT ${IS_CLEAN}) - message( - WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty" - ) - endif() - endif() - - cpm_add_subdirectory( - "${CPM_ARGS_NAME}" - "${DOWNLOAD_ONLY}" - "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - "${${CPM_ARGS_NAME}_BINARY_DIR}" - "${CPM_ARGS_EXCLUDE_FROM_ALL}" - "${CPM_ARGS_SYSTEM}" - "${CPM_ARGS_OPTIONS}" - ) - set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") - - # As the source dir is already cached/populated, we override the call to FetchContent. - set(CPM_SKIP_FETCH TRUE) - cpm_override_fetchcontent( - "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" - ) - - else() - # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but - # it should guarantee no commit hash get mis-detected. - if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) - cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) - if(NOT ${IS_HASH}) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) - endif() - endif() - - # remove timestamps so CMake will re-download the dependency - file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) - set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") - endif() + set(fetch_source_dir ${CPM_ARGS_SOURCE_DIR}) + if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) + get_filename_component( + fetch_source_dir ${CPM_ARGS_SOURCE_DIR} + REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR}) endif() - if(NOT "${DOWNLOAD_ONLY}") - cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") + # FetchContent args + set(fetchContentDeclareExtraArgs "") + if(${CPM_ARGS_EXCLUDE_FROM_ALL}) + list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) endif() - if(CPM_PACKAGE_LOCK_ENABLED) - if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) - cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") - elseif(CPM_ARGS_SOURCE_DIR) - cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") - else() - cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") - endif() + if(CPM_ARGS_SYSTEM) + list(APPEND fetchContentDeclareExtraArgs SYSTEM) endif() - cpm_message( - STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" - ) + if(DEFINED CPM_ARGS_SOURCE_SUBDIR) + list(APPEND fetchContentDeclareExtraArgs + SOURCE_SUBDIR ${CPM_ARGS_SOURCE_SUBDIR}) + endif() - if(NOT CPM_SKIP_FETCH) - # CMake 3.28 added EXCLUDE, SYSTEM (3.25), and SOURCE_SUBDIR (3.18) to FetchContent_Declare. - # Calling FetchContent_MakeAvailable will then internally forward these options to - # add_subdirectory. Up until these changes, we had to call FetchContent_Populate and - # add_subdirectory separately, which is no longer necessary and has been deprecated as of 3.30. - # A Bug in CMake prevents us to use the non-deprecated functions until 3.30.3. - set(fetchContentDeclareExtraArgs "") - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") - if(${CPM_ARGS_EXCLUDE_FROM_ALL}) - list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) - endif() - if(${CPM_ARGS_SYSTEM}) - list(APPEND fetchContentDeclareExtraArgs SYSTEM) - endif() - if(DEFINED CPM_ARGS_SOURCE_SUBDIR) - list(APPEND fetchContentDeclareExtraArgs SOURCE_SUBDIR ${CPM_ARGS_SOURCE_SUBDIR}) - endif() - # For CMake version <3.28 OPTIONS are parsed in cpm_add_subdirectory - if(CPM_ARGS_OPTIONS AND NOT DOWNLOAD_ONLY) - foreach(OPTION ${CPM_ARGS_OPTIONS}) - cpm_parse_option("${OPTION}") - set(${OPTION_KEY} "${OPTION_VALUE}") - endforeach() - endif() - endif() - cpm_declare_fetch( - "${CPM_ARGS_NAME}" ${fetchContentDeclareExtraArgs} "${CPM_ARGS_UNPARSED_ARGUMENTS}" - ) + if(CPM_ARGS_OPTIONS AND NOT DOWNLOAD_ONLY) + foreach(OPTION ${CPM_ARGS_OPTIONS}) + cpm_parse_option("${OPTION}") + set(${OPTION_KEY} "${OPTION_VALUE}") + endforeach() + endif() - cpm_fetch_package("${CPM_ARGS_NAME}" ${DOWNLOAD_ONLY} populated ${CPM_ARGS_UNPARSED_ARGUMENTS}) - if(CPM_SOURCE_CACHE AND download_directory) - file(LOCK ${download_directory}/../cmake.lock RELEASE) - endif() - if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.30.3") - cpm_add_subdirectory( - "${CPM_ARGS_NAME}" - "${DOWNLOAD_ONLY}" - "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - "${${CPM_ARGS_NAME}_BINARY_DIR}" - "${CPM_ARGS_EXCLUDE_FROM_ALL}" - "${CPM_ARGS_SYSTEM}" - "${CPM_ARGS_OPTIONS}" - ) - endif() - cpm_get_fetch_properties("${CPM_ARGS_NAME}") + if(NOT DOWNLOAD_ONLY) + cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") endif() - set(${CPM_ARGS_NAME}_ADDED YES) - cpm_export_variables("${CPM_ARGS_NAME}") -endfunction() + FetchContent_Declare(${CPM_ARGS_NAME} + ${fetchContentDeclareExtraArgs} + SOURCE_DIR ${fetch_source_dir} + DOWNLOAD_NO_PROGRESS TRUE) -# Fetch a previously declared package -macro(CPMGetPackage Name) - if(DEFINED "CPM_DECLARATION_${Name}") - CPMAddPackage(NAME ${Name}) + if(DOWNLOAD_ONLY) + FetchContent_Populate(${CPM_ARGS_NAME} + ${fetchContentDeclareExtraArgs} + SOURCE_DIR "${fetch_source_dir}" + BINARY_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" + SUBBUILD_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild" + DOWNLOAD_NO_PROGRESS TRUE) else() - message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available") + FetchContent_MakeAvailable(${CPM_ARGS_NAME}) endif() -endmacro() - -# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set -macro(cpm_export_variables name) - set(${name}_SOURCE_DIR - "${${name}_SOURCE_DIR}" - PARENT_SCOPE - ) - set(${name}_BINARY_DIR - "${${name}_BINARY_DIR}" - PARENT_SCOPE - ) - set(${name}_ADDED - "${${name}_ADDED}" - PARENT_SCOPE - ) - set(CPM_LAST_PACKAGE_NAME - "${name}" - PARENT_SCOPE - ) -endmacro() -# declares a package, so that any call to CPMAddPackage for the package name will use these -# arguments instead. Previous declarations will not be overridden. -macro(CPMDeclarePackage Name) - if(NOT DEFINED "CPM_DECLARATION_${Name}") - set("CPM_DECLARATION_${Name}" "${ARGN}") - endif() -endmacro() + FetchContent_GetProperties(${CPM_ARGS_NAME}) + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} + ${${lower_case_name}_SOURCE_DIR} + ${${lower_case_name}_BINARY_DIR}) -function(cpm_add_to_package_lock Name) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) - file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") - endif() -endfunction() + cpm_get_fetch_properties("${CPM_ARGS_NAME}") -function(cpm_add_comment_to_package_lock Name) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) - file(APPEND ${CPM_PACKAGE_LOCK_FILE} - "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" - ) - endif() + set(${CPM_ARGS_NAME}_ADDED YES) + cpm_export_variables("${CPM_ARGS_NAME}") endfunction() -# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to -# update it -macro(CPMUsePackageLock file) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) - if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) - include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) - endif() - if(NOT TARGET cpm-update-package-lock) - add_custom_target( - cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} - ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} - ) - endif() - set(CPM_PACKAGE_LOCK_ENABLED true) - endif() +# export variables available to the caller to the +# parent scope expects ${CPM_ARGS_NAME} to be set +macro(cpm_export_variables name) + set(${name}_SOURCE_DIR "${${name}_SOURCE_DIR}" PARENT_SCOPE) + set(${name}_BINARY_DIR "${${name}_BINARY_DIR}" PARENT_SCOPE) + set(${name}_ADDED "${${name}_ADDED}" PARENT_SCOPE) + set(CPM_LAST_PACKAGE_NAME "${name}" PARENT_SCOPE) endmacro() # registers a package that has been added to CPM function(CPMRegisterPackage PACKAGE VERSION) list(APPEND CPM_PACKAGES ${PACKAGE}) - set(CPM_PACKAGES - ${CPM_PACKAGES} - CACHE INTERNAL "" - ) - set("CPM_PACKAGE_${PACKAGE}_VERSION" - ${VERSION} - CACHE INTERNAL "" - ) + set(CPM_PACKAGES ${CPM_PACKAGES} CACHE INTERNAL "") + set("CPM_PACKAGE_${PACKAGE}_VERSION" ${VERSION}CACHE INTERNAL "") endfunction() # retrieve the current version of the package to ${OUTPUT} function(CPMGetPackageVersion PACKAGE OUTPUT) - set(${OUTPUT} - "${CPM_PACKAGE_${PACKAGE}_VERSION}" - PARENT_SCOPE - ) + set(${OUTPUT} "${CPM_PACKAGE_${PACKAGE}_VERSION}" PARENT_SCOPE) endfunction() -# declares a package in FetchContent_Declare -function(cpm_declare_fetch PACKAGE) - if(${CPM_DRY_RUN}) - cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") - return() - endif() - - FetchContent_Declare(${PACKAGE} ${ARGN}) -endfunction() - -# returns properties for a package previously defined by cpm_declare_fetch function(cpm_get_fetch_properties PACKAGE) - if(${CPM_DRY_RUN}) - return() - endif() + set(${PACKAGE}_SOURCE_DIR "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" + PARENT_SCOPE) - set(${PACKAGE}_SOURCE_DIR - "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" - PARENT_SCOPE - ) - set(${PACKAGE}_BINARY_DIR - "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" - PARENT_SCOPE - ) + set(${PACKAGE}_BINARY_DIR "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" + PARENT_SCOPE) endfunction() function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) - if(${CPM_DRY_RUN}) - return() - endif() - - set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR - "${source_dir}" - CACHE INTERNAL "" - ) - set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR - "${binary_dir}" - CACHE INTERNAL "" - ) -endfunction() - -# adds a package as a subdirectory if viable, according to provided options -function( - cpm_add_subdirectory - PACKAGE - DOWNLOAD_ONLY - SOURCE_DIR - BINARY_DIR - EXCLUDE - SYSTEM - OPTIONS -) - - if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) - set(addSubdirectoryExtraArgs "") - if(EXCLUDE) - list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) - endif() - if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") - # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM - list(APPEND addSubdirectoryExtraArgs SYSTEM) - endif() - if(OPTIONS) - foreach(OPTION ${OPTIONS}) - cpm_parse_option("${OPTION}") - set(${OPTION_KEY} "${OPTION_VALUE}") - endforeach() - endif() - set(CPM_OLD_INDENT "${CPM_INDENT}") - set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") - add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) - set(CPM_INDENT "${CPM_OLD_INDENT}") - endif() -endfunction() - -# downloads a previously declared package via FetchContent and exports the variables -# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope -function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated) - set(${populated} - FALSE - PARENT_SCOPE - ) - if(${CPM_DRY_RUN}) - cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)") - return() - endif() - - FetchContent_GetProperties(${PACKAGE}) - - string(TOLOWER "${PACKAGE}" lower_case_name) - - if(NOT ${lower_case_name}_POPULATED) - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") - if(DOWNLOAD_ONLY) - # MakeAvailable will call add_subdirectory internally which is not what we want when - # DOWNLOAD_ONLY is set. Populate will only download the dependency without adding it to the - # build - FetchContent_Populate( - ${PACKAGE} - SOURCE_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-src" - BINARY_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" - SUBBUILD_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild" - ${ARGN} - ) - else() - FetchContent_MakeAvailable(${PACKAGE}) - endif() - else() - FetchContent_Populate(${PACKAGE}) - endif() - set(${populated} - TRUE - PARENT_SCOPE - ) - endif() - - cpm_store_fetch_properties( - ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} - ) - - set(${PACKAGE}_SOURCE_DIR - ${${lower_case_name}_SOURCE_DIR} - PARENT_SCOPE - ) - set(${PACKAGE}_BINARY_DIR - ${${lower_case_name}_BINARY_DIR} - PARENT_SCOPE - ) + set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR "${source_dir}" CACHE INTERNAL "") + set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR "${binary_dir}" CACHE INTERNAL "") endfunction() # splits a package option @@ -1237,6 +313,7 @@ function(cpm_parse_option OPTION) string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") string(LENGTH "${OPTION}" OPTION_LENGTH) string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) + if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) # no value for key provided, assume user wants to set option to "ON" set(OPTION_VALUE "ON") @@ -1244,122 +321,7 @@ function(cpm_parse_option OPTION) math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) endif() - set(OPTION_KEY - "${OPTION_KEY}" - PARENT_SCOPE - ) - set(OPTION_VALUE - "${OPTION_VALUE}" - PARENT_SCOPE - ) -endfunction() - -# guesses the package version from a git tag -function(cpm_get_version_from_git_tag GIT_TAG RESULT) - string(LENGTH ${GIT_TAG} length) - if(length EQUAL 40) - # GIT_TAG is probably a git hash - set(${RESULT} - 0 - PARENT_SCOPE - ) - else() - string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) - set(${RESULT} - ${CMAKE_MATCH_1} - PARENT_SCOPE - ) - endif() -endfunction() - -# guesses if the git tag is a commit hash or an actual tag or a branch name. -function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) - string(LENGTH "${GIT_TAG}" length) - # full hash has 40 characters, and short hash has at least 7 characters. - if(length LESS 7 OR length GREATER 40) - set(${RESULT} - 0 - PARENT_SCOPE - ) - else() - if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") - set(${RESULT} - 1 - PARENT_SCOPE - ) - else() - set(${RESULT} - 0 - PARENT_SCOPE - ) - endif() - endif() -endfunction() - -function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) - set(oneValueArgs - NAME - FORCE - VERSION - GIT_TAG - DOWNLOAD_ONLY - GITHUB_REPOSITORY - GITLAB_REPOSITORY - BITBUCKET_REPOSITORY - GIT_REPOSITORY - SOURCE_DIR - FIND_PACKAGE_ARGUMENTS - NO_CACHE - SYSTEM - GIT_SHALLOW - EXCLUDE_FROM_ALL - SOURCE_SUBDIR - ) - set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - foreach(oneArgName ${oneValueArgs}) - if(DEFINED CPM_ARGS_${oneArgName}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - if(${oneArgName} STREQUAL "SOURCE_DIR") - string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} - ${CPM_ARGS_${oneArgName}} - ) - endif() - string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") - endif() - endforeach() - foreach(multiArgName ${multiValueArgs}) - if(DEFINED CPM_ARGS_${multiArgName}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") - foreach(singleOption ${CPM_ARGS_${multiArgName}}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") - endforeach() - endif() - endforeach() - - if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " ") - foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) - string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") - endforeach() - string(APPEND PRETTY_OUT_VAR "\n") - endif() - - set(${OUT_VAR} - ${PRETTY_OUT_VAR} - PARENT_SCOPE - ) + set(OPTION_KEY "${OPTION_KEY}" PARENT_SCOPE) + set(OPTION_VALUE "${OPTION_VALUE}" PARENT_SCOPE) endfunction() diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake index 94b0489432..3144f832c2 100644 --- a/CMakeModules/CPMUtil.cmake +++ b/CMakeModules/CPMUtil.cmake @@ -9,7 +9,22 @@ else() set(BUNDLED_DEFAULT OFF) endif() -set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/.cache/cpm" CACHE STRING "" FORCE) +# TODO: Handle nested cpmutil includes + +# Root directory detection +if (CMAKE_SCRIPT_MODE_FILE) + # In script mode, default to PWD + # Or you can specify one yourself + if (NOT DEFINED CPMUTIL_ROOT) + set(CPMUTIL_ROOT ${CMAKE_SOURCE_DIR}) + endif() +else() + # A project exists, we can use its directory + set(CPMUTIL_ROOT ${PROJECT_SOURCE_DIR}) +endif() + +set(CPM_SOURCE_CACHE "${CPMUTIL_ROOT}/.cache/cpm" + CACHE PATH "Directory to download CPM dependencies") option(CPMUTIL_FORCE_BUNDLED "Force bundled packages for all CPM depdendencies" ${BUNDLED_DEFAULT}) @@ -17,13 +32,17 @@ option(CPMUTIL_FORCE_BUNDLED option(CPMUTIL_FORCE_SYSTEM "Force system packages for all CPM dependencies" OFF) -set(CPMUTIL_PATCH_DIR "${PROJECT_SOURCE_DIR}/.patch" CACHE STRING +set(CPMUTIL_PATCH_DIR "${CPMUTIL_ROOT}/.patch" CACHE STRING "Directory containing patches for packages") include(CPM) # Rudimentary target architecture detection -if (NOT DEFINED ARCHITECTURE) +# This is in script mode, CI packages will be nonfunctional. TODO +if (NOT DEFINED CMAKE_SYSTEM_PROCESSOR) + message(DEBUG "[CPMUtil] No system architecture found, " + "CI packages are nonfunctional") +elseif (NOT DEFINED ARCHITECTURE) string(TOLOWER ${CMAKE_SYSTEM_PROCESSOR} processor) if (processor MATCHES "x86|amd64") set(CPMUTIL_AMD64 ON) @@ -44,26 +63,391 @@ else() endif() # cpmfile parsing -set(CPMUTIL_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json") +set(CPMUTIL_JSON_FILE "${CPMUTIL_ROOT}/cpmfile.json") if(EXISTS ${CPMUTIL_JSON_FILE}) file(READ ${CPMUTIL_JSON_FILE} CPMFILE_CONTENT) - if (NOT TARGET cpmfiles) - add_custom_target(cpmfiles) - endif() - target_sources(cpmfiles PRIVATE ${CPMUTIL_JSON_FILE}) - set_property(DIRECTORY APPEND PROPERTY - CMAKE_CONFIGURE_DEPENDS - "${CPMUTIL_JSON_FILE}") + # Script mode doesn't support add_custom_target etc. + if (NOT CMAKE_SCRIPT_MODE_FILE) + if (NOT TARGET cpmfiles) + add_custom_target(cpmfiles) + endif() + + target_sources(cpmfiles PRIVATE ${CPMUTIL_JSON_FILE}) + set_property(DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS + "${CPMUTIL_JSON_FILE}") + endif() else() - message(DEBUG "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE}" + message(DEBUG "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE} " "does not exist, AddJsonPackage will be a no-op") endif() -# Utility stuff -function(cpm_utils_message level name message) - message(${level} "[CPMUtil] ${name}: ${message}") +############# +# UTILITIES # +############# + +# Analogous to POSIX echo +macro(echo) + string(REPLACE ";" " " message "${ARGN}") + if (CMAKE_SCRIPT_MODE_FILE) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo + "${message}") + else() + message(STATUS "${message}") + endif() +endmacro() + +# Analogous to POSIX echo, but to stderr +macro(echo_error) + string(REPLACE ";" " " message "${ARGN}") + message(NOTICE "${message}") +endmacro() + +# Fatal error, accounting for script mode +macro(fatal) + if (CMAKE_SCRIPT_MODE_FILE) + echo_error(${ARGN}) + cmake_language(EXIT 1) + else() + message(FATAL_ERROR ${ARGN}) + endif() +endmacro() + +# Analogous to POSIX sleep +macro(sleep time) + execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${time}) +endmacro() + +# Analogous to GNU mktemp, with fallbacks +function(mktempdir out) + # shell out to system mktemp if available + find_program(MKTEMP_EXECUTABLE mktemp) + if (MKTEMP_EXECUTABLE) + execute_process(COMMAND mktemp -d + OUTPUT_VARIABLE dir + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE ret) + + if (ret EQUAL 0) + set(${out} "${dir}" PARENT_SCOPE) + return() + endif() + endif() + + string(RANDOM LENGTH 10 rand_str) + set(tmp_str "tmp.${rand_str}") + + # create something in /tmp if it exists + if(EXISTS "/tmp" AND IS_DIRECTORY "/tmp") + set(dir "/tmp/${tmp_str}") + file(MAKE_DIRECTORY "${dir}" RESULT res) + if (res EQUAL 0) + set(${out} "${dir}" PARENT_SCOPE) + return() + endif() + endif() + + # tmpdir does not exist, extremely legacy mode + set(dir "${CMAKE_CURRENT_LIST_DIR}/.tmp/${tmp_str}") + file(MAKE_DIRECTORY "${dir}" RESULT res) + if (res EQUAL 0) + set(${out} "${dir}" PARENT_SCOPE) + return() + endif() + + fatal("Fatal: Could not create temporary directory. " + "Check write permissions to the current directory") +endfunction() + +# Get a package's effective URL. +function(get_package_url) + set(oneValueArgs + GIT_HOST + REPO + VERSION + ARTIFACT + PACKAGE + + URL_OUT + GIT_URL_OUT) + + cmake_parse_arguments(ARG "" "${oneValueArgs}" "" ${ARGN}) + + if (NOT DEFINED ARG_URL_OUT) + fatal("get_package_url: URL_OUT is required") + endif() + + if(NOT DEFINED ARG_GIT_HOST) + set(ARG_GIT_HOST github.com) + endif() + + if(DEFINED ARG_REPO) + set(url https://${ARG_GIT_HOST}/${ARG_REPO}) + + if (DEFINED ARG_GIT_URL_OUT) + set(${ARG_GIT_URL_OUT} "${url}") + endif() + + if(DEFINED ARG_ARTIFACT) + set(url + "${url}/releases/download/${ARG_VERSION}/${ARG_ARTIFACT}") + else() + set(url "${url}/archive/${ARG_VERSION}.tar.gz") + endif() + else() + fatal("${package}: No URL or repository defined") + endif() + + set(${ARG_URL_OUT} "${url}") + + return(PROPAGATE ${ARG_URL_OUT} ${ARG_GIT_URL_OUT}) +endfunction() + +# Get a package's cache path. +function(get_cache_path package key out) + string(TOLOWER ${package} lower_name) + + # TODO: Figure out a sln to CPM_SOURCE_CACHE; CPMConfig.cmake? + set(${out} ${CPM_SOURCE_CACHE}/${lower_name}/${key}) + + return(PROPAGATE ${out}) +endfunction() + +# Download a URL to file, with a sha512 hash +# And retry 5 times +function(cpm_download url file) + list(LENGTH ARGN argn_len) + if(argn_len GREATER 0) + list(GET ARGN 0 hash) + set(args EXPECTED_HASH SHA512=${hash}) + else() + unset(args) + endif() + + foreach(i RANGE 5) + file(DOWNLOAD ${url} ${file} + ${args} + STATUS ret + LOG log) + + list(GET ret 0 code) + if (code EQUAL 0) + break() + endif() + + echo_error("Download failed. Trying again in 5 seconds") + sleep(5) + endforeach() + + # TODO: use return code or something + if (NOT code EQUAL 0) + fatal("Download for ${pkg_url} failed after 5 tries") + endif() +endfunction() + +# Wrapper around find_program that works with Git for Windows +macro(cpm_find_program) + # Windows needs additional paths for some utilities. + if (CMAKE_HOST_WIN32) + find_package(Git QUIET) + if(Git_FOUND) + # Search within the Git for Windows paths. + get_filename_component(extra_search_path + ${GIT_EXECUTABLE} DIRECTORY) + get_filename_component(extra_search_path_1up + ${extra_search_path} DIRECTORY) + get_filename_component(extra_search_path_2up + ${extra_search_path_1up} DIRECTORY) + + set(base_hints "${extra_search_path_1up}/usr/bin" + "${extra_search_path_2up}/usr/bin") + + # Also add core_perl to the paths, for perl commands like json_pp + set(hints "") + foreach(hint ${base_hints}) + list(APPEND hints + "${hint}" + "${hint}/core_perl") + endforeach() + + find_program(${ARGN} HINTS ${hints}) + endif() + + # If no Git is found, continue as normal + endif() + + find_program(${ARGN}) +endmacro() + +# Apply patches to a directory. +function(apply_patches patches dir) + cpm_find_program(PATCH_EXE patch) + if (NOT PATCH_EXE) + fatal("Could not find patch executable") + endif() + + foreach(patch ${patches}) + get_filename_component(patch_name ${patch} NAME) + echo("-- Applying patch ${patch_name}") + execute_process( + COMMAND ${PATCH_EXE} -p1 + INPUT_FILE ${patch} + WORKING_DIRECTORY ${dir}) + endforeach() +endfunction() + +# Fetches a file to the CPM source cache. +function(fetch_package) + set(oneValueArgs + URL + HASH + PATH + + PATCH_KEY) + + set(multiValueArgs PATCHES) + + set(optionArgs FORCE) + + cmake_parse_arguments(ARG + "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT DEFINED ARG_URL) + fatal("fetch_package: URL is required") + endif() + + if (NOT DEFINED ARG_HASH) + fatal("fetch_package: HASH is required") + endif() + + if (NOT DEFINED ARG_PATH) + fatal("fetch_package: PATH is required") + endif() + + if (NOT DEFINED ARG_PATCH_KEY) + compute_patch_key("${ARG_PATCHES}" ARG_PATCH_KEY) + endif() + + # refetch if cache is invalid or forced + needs_refetch(${ARG_PATH} "${ARG_PATCH_KEY}" CACHE_INVALID) + + if (ARG_FORCE OR CACHE_INVALID) + file(REMOVE_RECURSE ${ARG_PATH}) + else() + return() + endif() + + # Temporary directory. + mktempdir(TMP) + + # Get filename from URL + get_filename_component(base_filename ${ARG_URL} NAME) + + # Download + set(file ${TMP}/${base_filename}) + cpm_download(${ARG_URL} ${file} ${ARG_HASH}) + message(DEBUG "Downloaded ${base_filename}") + + # Extract the downloaded archive + # TODO: Moar error handling + set(dir ${TMP}/${base_filename}-extracted) + file(MAKE_DIRECTORY ${dir}) + + file(ARCHIVE_EXTRACT + INPUT ${file} + DESTINATION ${dir}) + + # This is copied near-verbatim from ExternalProject/extractfile.cmake.in + + # If there's just one subdirectory and nothing else, move it + file(GLOB contents "${dir}/*") + list(REMOVE_ITEM contents "${dir}/.DS_Store") + list(LENGTH contents n) + + # If n == 1 and contents points to a directory, this is a GitHub-style pack + # In this case contents points to the subdir which will get renamed + # If not, contents will point to the parent dir which will get renamed + if (NOT n EQUAL 1 OR NOT IS_DIRECTORY "${contents}") + set(contents "${dir}") + endif() + + file(REAL_PATH "${contents}" contents_abs) + + # paths + cmake_path(ABSOLUTE_PATH ARG_PATH + NORMALIZE + OUTPUT_VARIABLE abs_path) + + cmake_path(GET abs_path PARENT_PATH path_parent) + cmake_path(GET abs_path FILENAME path_name) + + # rename tmp dir + set(tmp_renamed "${TMP}/${path_name}") + file(RENAME "${contents_abs}" "${tmp_renamed}") + + # now copy + # TODO: Error handling beyond what cmake does???? + file(COPY ${tmp_renamed} DESTINATION ${path_parent}) + + # TODO: only echo this in script mode + message(DEBUG "Extracted to ${abs_path}") + + # Apply patches + apply_patches("${ARG_PATCHES}" "${abs_path}") + + # Write patch key + file(WRITE "${abs_path}/.cpm_patch_key" ${ARG_PATCH_KEY}) + + # done! :) + file(REMOVE_RECURSE ${TMP}) +endfunction() + +# compute a hash of all patch file contents +# if there are no patches, returns none +function(compute_patch_key patches out) + if(NOT patches) + set("${out}" "none" PARENT_SCOPE) + return() + endif() + + set(combined "") + foreach(PATCH ${patches}) + file(READ "${PATCH}" contents) + string(APPEND combined "${contents}") + endforeach() + + string(SHA512 key "${combined}") + set("${out}" "${key}" PARENT_SCOPE) +endfunction() + +# Check if a package needs to be redownloaded. This can occur if: +# - Patch key is missing or mismatched +# - Package fetch dir is missing +# path is the package cache path +function(needs_refetch path patch_key out) + set(patch_key_file "${path}/.cpm_patch_key") + + # download directory is empty or patch key file is missing + if (NOT EXISTS ${patch_key_file}) + set(${out} TRUE PARENT_SCOPE) + return() + endif() + + # compare patch key + file(READ "${patch_key_file}" current_patch_key) + if (NOT current_patch_key STREQUAL patch_key) + set(${out} TRUE PARENT_SCOPE) + else() + set(${out} FALSE PARENT_SCOPE) + endif() +endfunction() + +# Message +function(cpm_utils_message level) + string(REPLACE ";" " " message "${ARGN}") + message(${level} "[CPMUtil] ${message}") endfunction() # propagate a variable to parent scope @@ -71,6 +455,18 @@ macro(Propagate var) set(${var} ${${var}} PARENT_SCOPE) endmacro() +# Get cpmfile content +function(get_cpmfile_content out) + set(${out} "${CPMFILE_CONTENT}" PARENT_SCOPE) +endfunction() + +# Get absolute path to cpmfile +function(get_cpmfile_path out) + file(REAL_PATH ${CPMUTIL_JSON_FILE} ${out}) + return(PROPAGATE ${out}) +endfunction() + +# idk function(array_to_list array length out) math(EXPR range "${length} - 1") @@ -83,11 +479,22 @@ function(array_to_list array length out) set("${out}" "${NEW_LIST}" PARENT_SCOPE) endfunction() -function(get_json_element object out member default) +# json +function(get_json_element object out member) + list(LENGTH ARGN argn_len) + if(argn_len GREATER 0) + list(GET ARGN 0 default) + endif() + string(JSON out_type ERROR_VARIABLE err TYPE "${object}" ${member}) if(err) - set("${out}" "${default}" PARENT_SCOPE) + if (DEFINED default) + set("${out}" "${default}" PARENT_SCOPE) + else() + unset("${out}" PARENT_SCOPE) + endif() + return() endif() @@ -101,6 +508,13 @@ function(get_json_element object out member default) set("${out}" "${outvar}" PARENT_SCOPE) endfunction() +# register a package to CPMUtil's global registry +function(cpmutil_register_package name url version) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${name}) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${url}) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS "${version}") +endfunction() + # Determine whether or not a package has a viable system candidate. function(SystemPackageViable JSON_NAME) string(JSON object GET "${CPMFILE_CONTENT}" "${JSON_NAME}") @@ -108,14 +522,17 @@ function(SystemPackageViable JSON_NAME) parse_object(${object}) string(REPLACE " " ";" find_args "${find_args}") - if (${package}_FORCE_BUNDLED) + if(${package}_FORCE_BUNDLED OR + (CPMUTIL_FORCE_BUNDLED + AND NOT ${package}_FORCE_SYSTEM + AND NOT CPMUTIL_FORCE_SYSTEM)) set(${package}_FOUND OFF) else() - find_package(${package} ${version} ${find_args} QUIET NO_POLICY_SCOPE) + find_package(${package} ${min_version} ${find_args} QUIET NO_POLICY_SCOPE) endif() - set(${pkg}_VIABLE ${${package}_FOUND} PARENT_SCOPE) - set(${pkg}_PACKAGE ${package} PARENT_SCOPE) + set(${JSON_NAME}_VIABLE ${${package}_FOUND} PARENT_SCOPE) + set(${JSON_NAME}_PACKAGE ${package} PARENT_SCOPE) endfunction() # Add several packages such that if one is bundled, @@ -166,19 +583,40 @@ function(AddDependentPackages) endforeach() endfunction() +# process version/artifact replacements +macro(process_version_replacements) + # numeric version text replacement + if (numeric_version) + string(REPLACE "%NUMERIC_VERSION%" "${numeric_version}" + version ${version}) + string(REPLACE "%NUMERIC_VERSION%" "${numeric_version}" + artifact ${artifact}) + endif() + + # version text replacement + if (artifact) + string(REPLACE "%VERSION%" "${version}" + artifact ${artifact}) + endif() +endmacro() + # json util macro(parse_object object) get_json_element("${object}" package package ${JSON_NAME}) - get_json_element("${object}" repo repo "") + get_json_element("${object}" repo repo) get_json_element("${object}" ci ci OFF) - get_json_element("${object}" version version "") - get_json_element("${object}" min_version min_version "") + get_json_element("${object}" version version) + get_json_element("${object}" min_version min_version) get_json_element("${object}" git_host git_host "github.com") + if (NOT version) + cpm_utils_message(FATAL_ERROR "${JSON_NAME}: version is required") + endif() + if(ci) get_json_element("${object}" name name "${JSON_NAME}") get_json_element("${object}" extension extension "tar.zst") - get_json_element("${object}" raw_disabled disabled_platforms "") + get_json_element("${object}" raw_disabled disabled_platforms) if(raw_disabled) array_to_list("${raw_disabled}" @@ -187,34 +625,21 @@ macro(parse_object object) set(disabled_platforms "") endif() else() - get_json_element("${object}" hash hash "") - get_json_element("${object}" sha sha "") - get_json_element("${object}" url url "") - get_json_element("${object}" tag tag "") - get_json_element("${object}" artifact artifact "") - get_json_element("${object}" source_subdir source_subdir "") + # TODO: correct hash if missing + get_json_element("${object}" hash hash) + get_json_element("${object}" url url) + get_json_element("${object}" artifact artifact) + get_json_element("${object}" source_subdir source_subdir) get_json_element("${object}" bundled bundled "unset") - get_json_element("${object}" find_args find_args "") - get_json_element("${object}" raw_patches patches "") - - # okay here comes the fun part: REPLACEMENTS! - # first: tag gets %VERSION% replaced if applicable, - # with version - # second: artifact gets %VERSION% and %TAG% replaced - # accordingly (same rules for VERSION) - - # TODO(crueter): fmt module for cmake - if(tag) - string(REPLACE "%VERSION%" "${version}" tag ${tag}) - endif() + get_json_element("${object}" numeric_version numeric_version) + get_json_element("${object}" find_args find_args) + get_json_element("${object}" skip_updates skip_updates OFF) + get_json_element("${object}" raw_patches patches) - if(artifact) - string(REPLACE "%VERSION%" "${version}" - artifact ${artifact}) - string(REPLACE "%TAG%" "${tag}" artifact ${artifact}) - endif() + process_version_replacements() # format patchdir + unset(patches) if(raw_patches) math(EXPR range "${raw_patches_LENGTH} - 1") @@ -224,8 +649,9 @@ macro(parse_object object) set(full_patch "${CPMUTIL_PATCH_DIR}/${JSON_NAME}/${_patch}") if(NOT EXISTS ${full_patch}) - cpm_utils_message(FATAL_ERROR ${JSON_NAME} - "specifies patch ${full_patch} which does not exist") + cpm_utils_message(FATAL_ERROR + "${JSON_NAME} specifies patch" + "${full_patch} which does not exist") endif() list(APPEND patches "${full_patch}") @@ -234,8 +660,9 @@ macro(parse_object object) # end format patchdir # options - get_json_element("${object}" raw_options options "") + get_json_element("${object}" raw_options options) + unset(options) if(raw_options) array_to_list("${raw_options}" ${raw_options_LENGTH} options) endif() @@ -250,6 +677,29 @@ macro(parse_object object) endif() endmacro() +# Get the JSON object for a key. Outputs to "object" +function(get_json_object key) + if(NOT DEFINED CPMFILE_CONTENT) + cpm_utils_message(FATAL_ERROR "${key}: No cpmfile present") + endif() + + if(NOT DEFINED key) + cpm_utils_message(FATAL_ERROR "No JSON key specified") + endif() + + string(JSON object ERROR_VARIABLE + err GET "${CPMFILE_CONTENT}" "${key}") + + if(err) + cpm_utils_message(FATAL_ERROR "${key} not found in cpmfile\n" + "${err}") + endif() + + Propagate(object) +endfunction() + +# Get the download URL given the parameters. + # The preferred usage function(AddJsonPackage) set(oneValueArgs @@ -276,25 +726,10 @@ function(AddJsonPackage) set(JSON_NAME "${ARGV0}") endif() - if(NOT DEFINED CPMFILE_CONTENT) - cpm_utils_message(FATAL_ERROR ${name} - "No cpmfile present") - return() - endif() - - if(NOT DEFINED JSON_NAME) - cpm_utils_message(FATAL_ERROR "json package" "No name specified") - endif() - - string(JSON object ERROR_VARIABLE - err GET "${CPMFILE_CONTENT}" "${JSON_NAME}") - - if(err) - cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile") - endif() - + get_json_object("${JSON_NAME}") parse_object(${object}) + unset(EXTRA_ARGS) if (JSON_MODULE_PATH) list(APPEND EXTRA_ARGS MODULE_PATH) endif() @@ -310,7 +745,6 @@ function(AddJsonPackage) REPO "${repo}" PACKAGE "${package}" EXTENSION "${extension}" - MIN_VERSION "${min_version}" DISABLED_PLATFORMS "${disabled_platforms}" GIT_HOST "${git_host}" @@ -327,7 +761,6 @@ function(AddJsonPackage) MIN_VERSION "${min_version}" URL "${url}" HASH "${hash}" - SHA "${sha}" REPO "${repo}" PATCHES "${patches}" OPTIONS "${options}" @@ -339,7 +772,6 @@ function(AddJsonPackage) GIT_HOST "${git_host}" ARTIFACT "${artifact}" - TAG "${tag}" ${EXTRA_ARGS}) endif() @@ -351,24 +783,23 @@ function(AddJsonPackage) Propagate(CMAKE_PREFIX_PATH) endfunction() +# Internal function function(AddPackage) - cpm_set_policies() set(EXTRA_ARGS "") set(oneValueArgs NAME - VERSION MIN_VERSION GIT_HOST REPO - TAG + VERSION ARTIFACT - SHA HASH URL + CUSTOM_KEY SOURCE_SUBDIR BUNDLED_PACKAGE @@ -386,7 +817,11 @@ function(AddPackage) "${ARGN}") if(NOT DEFINED PKG_ARGS_NAME) - cpm_utils_message(FATAL_ERROR "package" "No package name defined") + cpm_utils_message(FATAL_ERROR "AddPackage: NAME is required") + endif() + + if(NOT DEFINED PKG_ARGS_VERSION) + cpm_utils_message(FATAL_ERROR "${PKG_ARGS_NAME}: VERSION is required") endif() set(${PKG_ARGS_NAME}_CUSTOM_DIR "" CACHE STRING @@ -396,176 +831,156 @@ function(AddPackage) option(${PKG_ARGS_NAME}_FORCE_BUNDLED "Force the bundled package for ${PKG_ARGS_NAME}") - if (DEFINED ${PKG_ARGS_NAME}_CUSTOM_DIR AND - NOT ${PKG_ARGS_NAME}_CUSTOM_DIR STREQUAL "") - set(CPM_${PKG_ARGS_NAME}_SOURCE ${${PKG_ARGS_NAME}_CUSTOM_DIR}) - endif() - - # TODO: See if this can be delegated to subshells - if(NOT DEFINED PKG_ARGS_GIT_HOST) - set(git_host github.com) + # URL parsing + if (NOT DEFINED PKG_ARGS_URL) + get_package_url(URL_OUT "PKG_ARGS_URL" + GIT_URL_OUT "pkg_git_url" + GIT_HOST "${PKG_ARGS_GIT_HOST}" + REPO "${PKG_ARGS_REPO}" + VERSION "${PKG_ARGS_VERSION}" + ARTIFACT "${PKG_ARGS_ARTIFACT}" + PACKAGE "${PKG_ARGS_NAME}") else() - set(git_host ${PKG_ARGS_GIT_HOST}) - endif() - - if(DEFINED PKG_ARGS_URL) - set(pkg_url ${PKG_ARGS_URL}) - set(pkg_git_url ${pkg_url}) - elseif(DEFINED PKG_ARGS_REPO) - set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO}) - - if(DEFINED PKG_ARGS_SHA) - set(pkg_url "${pkg_git_url}/archive/${PKG_ARGS_SHA}.tar.gz") - elseif(DEFINED PKG_ARGS_TAG) - set(tag "${PKG_ARGS_TAG}") - if(DEFINED PKG_ARGS_ARTIFACT) - set(artifact "${PKG_ARGS_ARTIFACT}") - set(pkg_url - "${pkg_git_url}/releases/download/${tag}/${artifact}") - else() - set(pkg_url - "${pkg_git_url}/archive/refs/tags/${tag}.tar.gz") - endif() - endif() - else() - cpm_utils_message(FATAL_ERROR ${PKG_ARGS_NAME} - "No URL or repository defined") + set(pkg_git_url ${PKG_ARGS_URL}) endif() - cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "Download URL is ${pkg_url}") + cpm_utils_message(DEBUG "${PKG_ARGS_NAME} download URL is ${PKG_ARGS_URL}") - if(DEFINED PKG_ARGS_SHA) - string(SUBSTRING ${PKG_ARGS_SHA} 0 4 pkg_key) - elseif(DEFINED PKG_ARGS_VERSION) - set(pkg_key ${PKG_ARGS_VERSION}) - elseif(DEFINED PKG_ARGS_TAG) - set(pkg_key ${PKG_ARGS_TAG}) - elseif(DEFINED PKG_ARGS_MIN_VERSION) - set(pkg_key ${PKG_ARGS_MIN_VERSION}) - else() - cpm_utils_message(FATAL_ERROR ${PKG_ARGS_NAME} - "Could not determine cache key") - endif() - - if(DEFINED PKG_ARGS_HASH) - set(pkg_hash "SHA512=${PKG_ARGS_HASH}") - else() - cpm_utils_message(FATAL_ERROR ${PKG_ARGS_NAME} - "No hash defined") + if(NOT DEFINED PKG_ARGS_HASH) + cpm_utils_message(FATAL_ERROR "${PKG_ARGS_NAME}: No hash defined") endif() - macro(set_precedence local force) - set(CPM_USE_LOCAL_PACKAGES ${local}) - set(CPM_LOCAL_PACKAGES_ONLY ${force}) - endmacro() - #[[ Precedence: - - package_FORCE_SYSTEM - - package_FORCE_BUNDLED - - CPMUTIL_FORCE_SYSTEM - - CPMUTIL_FORCE_BUNDLED - - BUNDLED_PACKAGE - - default to allow local + - FORCE_BUNDLED_PACKAGE (caller override) + - package_FORCE_SYSTEM, package_FORCE_BUNDLED (user option) + - CPMUTIL_FORCE_SYSTEM, CPMUTIL_FORCE_BUNDLED (global option) + - BUNDLED_PACKAGE (json default) + - default to allow system ]] if(PKG_ARGS_FORCE_BUNDLED_PACKAGE) - set_precedence(OFF OFF) + set(use_system OFF) elseif(${PKG_ARGS_NAME}_FORCE_SYSTEM) - set_precedence(ON ON) + set(use_system ON) + set(force_system ON) elseif(${PKG_ARGS_NAME}_FORCE_BUNDLED) - set_precedence(OFF OFF) + set(use_system OFF) elseif(CPMUTIL_FORCE_SYSTEM) - set_precedence(ON ON) + set(use_system ON) + set(force_system ON) elseif(CPMUTIL_FORCE_BUNDLED) - set_precedence(OFF OFF) + set(use_system OFF) elseif(DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset") if(PKG_ARGS_BUNDLED_PACKAGE) - set(local OFF) + set(use_system OFF) else() - set(local ON) + set(use_system ON) endif() - - set_precedence(${local} OFF) else() - set_precedence(ON OFF) + set(use_system ON) endif() - if(DEFINED PKG_ARGS_MIN_VERSION) - list(APPEND EXTRA_ARGS - VERSION ${PKG_ARGS_MIN_VERSION}) - endif() + # find_package nonsense + if(use_system) + set(find_args ${PKG_ARGS_NAME}) + if(DEFINED PKG_ARGS_MIN_VERSION) + list(APPEND find_args ${PKG_ARGS_MIN_VERSION}) + endif() - if (PKG_ARGS_FIND_PACKAGE_ARGUMENTS) - list(APPEND EXTRA_ARGS - FIND_PACKAGE_ARGUMENTS "${PKG_ARGS_FIND_PACKAGE_ARGUMENTS}") - endif() + if (DEFINED PKG_ARGS_FIND_PACKAGE_ARGUMENTS) + string(REPLACE " " ";" + passed_find_args + "${PKG_ARGS_FIND_PACKAGE_ARGUMENTS}") - if (PKG_ARGS_PATCHES) - list(APPEND EXTRA_ARGS - PATCHES "${PKG_ARGS_PATCHES}") + set(find_args "${find_args};${passed_find_args}") + endif() + + find_package(${find_args} QUIET) + + if(${PKG_ARGS_NAME}_FOUND) + if(DEFINED ${PKG_ARGS_NAME}_VERSION) + set(sys_ver ${${PKG_ARGS_NAME}_VERSION}) + else() + set(sys_ver ${PKG_ARGS_VERSION}) + endif() + + message(STATUS + "[CPMUtil] Using system package ${PKG_ARGS_NAME}@${sys_ver}") + + CPMRegisterPackage(${PKG_ARGS_NAME} "${sys_ver}") + + cpmutil_register_package( + ${PKG_ARGS_NAME} + ${pkg_git_url} + "${sys_ver} (system)") + + set(${PKG_ARGS_NAME}_ADDED NO PARENT_SCOPE) + + return() + endif() + + if(force_system) + string(REPLACE ";" " " str_find_args "${find_args}") + message(FATAL_ERROR "[CPMUtil] ${PKG_ARGS_NAME} not found via " + "find_package(${str_find_args})") + endif() endif() + # extra options if (PKG_ARGS_OPTIONS) - list(APPEND EXTRA_ARGS - OPTIONS "${PKG_ARGS_OPTIONS}") + list(APPEND EXTRA_ARGS OPTIONS "${PKG_ARGS_OPTIONS}") endif() if (PKG_ARGS_SOURCE_SUBDIR) - list(APPEND EXTRA_ARGS - SOURCE_SUBDIR "${PKG_ARGS_SOURCE_SUBDIR}") + list(APPEND EXTRA_ARGS SOURCE_SUBDIR "${PKG_ARGS_SOURCE_SUBDIR}") endif() if (PKG_ARGS_DOWNLOAD_ONLY OR PKG_ARGS_MODULE_PATH) list(APPEND EXTRA_ARGS DOWNLOAD_ONLY ON) endif() + cpm_utils_message(STATUS + "Using bundled package ${PKG_ARGS_NAME}@${PKG_ARGS_VERSION}") + + # Download/extract package + if (${PKG_ARGS_NAME}_CUSTOM_DIR STREQUAL "") + if (DEFINED PKG_ARGS_CUSTOM_KEY) + set(key ${PKG_ARGS_CUSTOM_KEY}) + else() + set(key ${PKG_ARGS_VERSION}) + endif() + + # cache path + get_cache_path(${PKG_ARGS_NAME} ${key} cache_path) + + fetch_package( + URL "${PKG_ARGS_URL}" + HASH "${PKG_ARGS_HASH}" + PATH "${cache_path}" + PATCHES "${PKG_ARGS_PATCHES}") + + set(source_dir "${cache_path}") + else() + set(source_dir "${${PKG_ARGS_NAME}_CUSTOM_DIR}") + endif() + CPMAddPackage( NAME ${PKG_ARGS_NAME} - URL ${pkg_url} - URL_HASH ${pkg_hash} - CUSTOM_CACHE_KEY ${pkg_key} + VERSION ${PKG_ARGS_VERSION} + SOURCE_DIR ${source_dir} EXCLUDE_FROM_ALL ON ${EXTRA_ARGS} - ${PKG_ARGS_UNPARSED_ARGUMENTS}) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${PKG_ARGS_NAME}) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${pkg_git_url}) - - if(${PKG_ARGS_NAME}_ADDED) - if(DEFINED PKG_ARGS_SHA) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - ${PKG_ARGS_SHA}) - elseif(DEFINED PKG_ARGS_VERSION) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - ${PKG_ARGS_VERSION}) - elseif(DEFINED PKG_ARGS_TAG) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - ${PKG_ARGS_TAG}) - elseif(DEFINED PKG_ARGS_MIN_VERSION) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - ${PKG_ARGS_MIN_VERSION}) - else() - cpm_utils_message(WARNING ${PKG_ARGS_NAME} - "Package has no specified sha, tag, or version") - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS "unknown") - endif() - else() - if(DEFINED CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION AND NOT - "${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION}" STREQUAL "") - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - "${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION} (system)") - else() - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - "unknown (system)") - endif() - endif() + cpmutil_register_package( + ${PKG_ARGS_NAME} + ${pkg_git_url} + ${PKG_ARGS_VERSION}) - # pass stuff to parent scope - Propagate(${PKG_ARGS_NAME}_ADDED) + set(${PKG_ARGS_NAME}_ADDED YES PARENT_SCOPE) Propagate(${PKG_ARGS_NAME}_SOURCE_DIR) Propagate(${PKG_ARGS_NAME}_BINARY_DIR) @@ -597,24 +1012,20 @@ function(AddCIPackage) "${multiValueArgs}" ${ARGN}) - # TODO: use cpm_utils_message if(NOT DEFINED PKG_ARGS_VERSION) - message(FATAL_ERROR "[CPMUtil] VERSION is required") + cpm_utils_message(FATAL_ERROR "VERSION is required") endif() + if(NOT DEFINED PKG_ARGS_NAME) - message(FATAL_ERROR "[CPMUtil] NAME is required") + cpm_utils_message(FATAL_ERROR "NAME is required") endif() + if(NOT DEFINED PKG_ARGS_REPO) - message(FATAL_ERROR "[CPMUtil] REPO is required") - endif() - if(NOT DEFINED PKG_ARGS_PACKAGE) - message(FATAL_ERROR "[CPMUtil] PACKAGE is required") + cpm_utils_message(FATAL_ERROR "REPO is required") endif() - if(NOT DEFINED PKG_ARGS_CMAKE_FILENAME) - set(ARTIFACT_CMAKE ${PKG_ARGS_NAME}) - else() - set(ARTIFACT_CMAKE ${PKG_ARGS_CMAKE_FILENAME}) + if(NOT DEFINED PKG_ARGS_PACKAGE) + cpm_utils_message(FATAL_ERROR "PACKAGE is required") endif() if(NOT DEFINED PKG_ARGS_EXTENSION) @@ -629,10 +1040,6 @@ function(AddCIPackage) set(ARTIFACT_GIT_HOST "${PKG_ARGS_GIT_HOST}") endif() - if(DEFINED PKG_ARGS_MIN_VERSION) - set(ARTIFACT_MIN_VERSION ${PKG_ARGS_MIN_VERSION}) - endif() - if(DEFINED PKG_ARGS_DISABLED_PLATFORMS) set(DISABLED_PLATFORMS ${PKG_ARGS_DISABLED_PLATFORMS}) endif() @@ -659,7 +1066,7 @@ function(AddCIPackage) elseif(APPLE) set(platname macos) else() - cpm_utils_message(WARNING ${PKG_ARGS_NAME} + cpm_utils_message(WARNING "Unsupported platform ${CMAKE_SYSTEM_NAME} for CI packages") endif() @@ -674,7 +1081,7 @@ function(AddCIPackage) elseif(ANDROID AND CPMUTIL_AMD64) set(archname x86_64) else() - cpm_utils_message(WARNING ${PKG_ARGS_NAME} + cpm_utils_message(WARNING "Unsupported platform/arch combo for CI packages") endif() @@ -692,7 +1099,7 @@ function(AddCIPackage) endif() # download sha512sum file - # TODO: + # TODO: CI pkgs set(sha512sum_url "https://${ARTIFACT_GIT_HOST}/${ARTIFACT_REPO}/releases/download/v${ARTIFACT_VERSION}/${ARTIFACT}.sha512sum") set(sha512sum_file @@ -714,9 +1121,9 @@ function(AddCIPackage) AddPackage( NAME ${ARTIFACT_PACKAGE} REPO ${ARTIFACT_REPO} - TAG "v${ARTIFACT_VERSION}" - MIN_VERSION ${ARTIFACT_VERSION} + VERSION "v${ARTIFACT_VERSION}" ARTIFACT ${ARTIFACT} + CUSTOM_KEY "${ARTIFACT_VERSION}-${pkgname}" HASH ${sha512sum_hash} FORCE_BUNDLED_PACKAGE ON ${EXTRA_ARGS}) @@ -725,7 +1132,8 @@ function(AddCIPackage) Propagate(${ARTIFACT_PACKAGE}_SOURCE_DIR) Propagate(CMAKE_PREFIX_PATH) else() - find_package(${ARTIFACT_PACKAGE} ${ARTIFACT_MIN_VERSION} REQUIRED) + cpm_utils_message(FATAL_ERROR "${ARTIFACT_NAME}:" + "Unsupported platform ${pkgname} for CI package") endif() endfunction() @@ -743,7 +1151,6 @@ function(AddQt repo version) NAME qt PACKAGE Qt6 VERSION ${version} - MIN_VERSION 6 REPO ${repo} DISABLED_PLATFORMS android-x86_64 android-aarch64 diff --git a/cpmfile.json b/cpmfile.json index 5bfb7ebb63..cac6c357b0 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -3,11 +3,10 @@ "hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a", "min_version": "0.9.1", "repo": "lioncash/biscuit", - "tag": "v%VERSION%", - "version": "0.19.0" + "version": "v0.19.0" }, "boost": { - "artifact": "%TAG%-cmake.tar.xz", + "artifact": "%VERSION%-cmake.tar.xz", "find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem", "hash": "6ae6e94664fe7f2fb01976b59b276ac5df8085c7503fa829d810fbfe495960cfec44fa2c36e2cb23480bc19c956ed199d4952b02639a00a6c07625d4e7130c2d", "min_version": "1.57", @@ -16,15 +15,13 @@ "0001-clang-cl.patch" ], "repo": "boostorg/boost", - "tag": "boost-%VERSION%", - "version": "1.90.0" + "version": "boost-1.90.0" }, "boost_headers": { "bundled": true, "hash": "4ef845775e2277a8104ded6ddf749aa262ce52cf8438042869a048f9a0156dd772fbbcfa74efa1378fecef339b7286f6fe4b4feb5c45d49966b35d08e3e83507", "repo": "boostorg/headers", - "tag": "boost-%VERSION%", - "version": "1.90.0" + "version": "boost-1.90.0" }, "catch2": { "hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35", @@ -34,8 +31,7 @@ "0001-solaris-isnan-fix.patch" ], "repo": "catchorg/Catch2", - "tag": "v%VERSION%", - "version": "3.13.0" + "version": "v3.13.0" }, "cpp-jwt": { "find_args": "CONFIG", @@ -48,8 +44,7 @@ "0001-fix-missing-decl.patch" ], "repo": "arun11299/cpp-jwt", - "sha": "7f24eb4c32", - "version": "1.5.1" + "version": "7f24eb4c32" }, "cubeb": { "find_args": "CONFIG", @@ -61,31 +56,26 @@ "BUNDLE_SPEEX ON" ], "repo": "mozilla/cubeb", - "sha": "fa02160712", - "version": "0.0.0" + "version": "fa02160712" }, "discord-rpc": { "find_args": "MODULE", "hash": "8213c43dcb0f7d479f5861091d111ed12fbdec1e62e6d729d65a4bc181d82f48a35d5fd3cd5c291f2393ac7c9681eabc6b76609755f55376284c8a8d67e148f3", "package": "DiscordRPC", "repo": "eden-emulator/discord-rpc", - "sha": "0d8b2d6a37", - "version": "3.4.1" + "version": "0d8b2d6a37" }, "enet": { "find_args": "MODULE", "hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9", "min_version": "1.3", "repo": "lsalzman/enet", - "tag": "v%VERSION%", - "version": "1.3.18" + "version": "v1.3.18" }, "ffmpeg": { - "bundled": true, "hash": "ed177621176b3961bdcaa339187d3a7688c1c8b060b79c4bb0257cbc67ad7021ae5d5adca5303b45625abbbe3d9aafdd87ce777b8690ac295290d744c875489a", "repo": "FFmpeg/FFmpeg", - "sha": "c7b5f1537d", - "version": "8.0.1" + "version": "c7b5f1537d" }, "ffmpeg-ci": { "ci": true, @@ -99,23 +89,20 @@ "hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2", "min_version": "8", "repo": "fmtlib/fmt", - "tag": "%VERSION%", "version": "12.1.0" }, "frozen": { "hash": "b8dfe741c82bc178dfc9749d4ab5a130cee718d9ee7b71d9b547cf5f7f23027ed0152ad250012a8546399fcc1e12187efc68d89d6731256c4d2df7d04eef8d5c", "package": "frozen", "repo": "serge-sans-paille/frozen", - "sha": "61dce5ae18", - "version": "1.2.0" + "version": "61dce5ae18" }, "gamemode": { "find_args": "MODULE", "hash": "e87ec14ed3e826d578ebf095c41580069dda603792ba91efa84f45f4571a28f4d91889675055fd6f042d7dc25b0b9443daf70963ae463e38b11bcba95f4c65a9", "min_version": "1.7", "repo": "FeralInteractive/gamemode", - "sha": "ce6fe122f3", - "version": "1.8.2" + "version": "ce6fe122f3" }, "httplib": { "find_args": "MODULE GLOBAL", @@ -128,23 +115,20 @@ "0001-mingw.patch" ], "repo": "yhirose/cpp-httplib", - "tag": "v%VERSION%", - "version": "0.46.0" + "version": "v0.46.0" }, "lagoon": { "hash": "b9380f99c6effaeccc6d8f81d4942e852c11ad28613df637e155451556ae5826f93765bee57a5c87a9740d2bd1db463ad0f55a947772fe9d57eeabae3efa373e", "repo": "loongson-community/lagoon", - "tag": "%VERSION%", "version": "1.0.0" }, "libadrenotools": { "hash": "f6526620cb752876edc5ed4c0925d57b873a8218ee09ad10859ee476e9333259784f61c1dcc55a2bcba597352d18aff22cd2e4c1925ec2ae94074e09d7da2265", "patches": [ - "0001-linkerns-cpm.patch" + "0001-use-cpmfile-def-for-linkernsbypass.patch" ], "repo": "eden-emulator/libadrenotools", - "sha": "8ba23b42d7", - "version": "1.0.0" + "version": "8ba23b42d7" }, "libusb": { "find_args": "MODULE", @@ -153,53 +137,52 @@ "0001-netbsd-gettime.patch" ], "repo": "libusb/libusb", - "tag": "v%VERSION%", - "version": "1.0.29" + "version": "v1.0.29" + }, + "linkernsbypass": { + "bundled": "true", + "hash": "bbe3f1f08e2bc7172b36e8f052912cc374289fc9a8e5a39ae7547a0c232de8f57ba24883451896b7a9a5d1be0e4de5c5b0f70c2022eda18d7d5a754847521800", + "repo": "bylaws/liblinkernsbypass", + "version": "aa3975893d" }, "llvm-mingw": { "artifact": "clang-rt-builtins.tar.zst", "git_host": "git.eden-emu.dev", "hash": "d902392caf94e84f223766e2cc51ca5fab6cae36ab8dc6ef9ef6a683ab1c483bfcfe291ef0bd38ab16a4ecc4078344fa8af72da2f225ab4c378dee23f6186181", "repo": "eden-emu/llvm-mingw", - "tag": "%VERSION%", "version": "20250828" }, "lz4": { "hash": "35c21a5d9cfb5bbf314a5321d02b36819491d2ee3cf8007030ca09d13ca4dae672247b7aeab553e973093604fc48221cb03dc92197c6efe8fc3746891363fdab", "name": "lz4", "repo": "lz4/lz4", - "sha": "ebb370ca83", "source_subdir": "build/cmake", - "version": "1.10.0" + "version": "ebb370ca83" }, "moltenvk": { "artifact": "MoltenVK-macOS.tar", "bundled": true, "hash": "5695b36ca5775819a71791557fcb40a4a5ee4495be6b8442e0b666d0c436bec02aae68cc6210183f7a5c986bdbec0e117aecfad5396e496e9c2fd5c89133a347", "repo": "V380-Ori/Ryujinx.MoltenVK", - "tag": "v%VERSION%-ryujinx", - "version": "1.4.1" + "version": "v1.4.1-ryujinx" }, "nlohmann": { "hash": "6cc1e86261f8fac21cc17a33da3b6b3c3cd5c116755651642af3c9e99bb3538fd42c1bd50397a77c8fb6821bc62d90e6b91bcdde77a78f58f2416c62fc53b97d", "min_version": "3.8", "package": "nlohmann_json", "repo": "nlohmann/json", - "tag": "v%VERSION%", - "version": "3.12.0" + "version": "v3.12.0" }, "oaknut": { "hash": "9697e80a7d5d9bcb3ce51051a9a24962fb90ca79d215f1f03ae6b58da8ba13a63b5dda1b4dde3d26ac6445029696b8ef2883f4e5a777b342bba01283ed293856", "min_version": "2.0.1", "repo": "eden-emulator/oaknut", - "tag": "v%VERSION%", - "version": "2.0.3" + "version": "v2.0.3" }, "oboe": { "bundled": true, "hash": "ce4011afe7345370d4ead3b891cd69a5ef224b129535783586c0ca75051d303ed446e6c7f10bde8da31fff58d6e307f1732a3ffd03b249f9ef1fd48fd4132715", "repo": "google/oboe", - "tag": "%VERSION%", "version": "1.10.0" }, "openssl": { @@ -210,8 +193,7 @@ "0001-add-bundled-cert.patch" ], "repo": "openssl/openssl", - "tag": "openssl-%VERSION%", - "version": "3.6.2" + "version": "openssl-3.6.2" }, "openssl-ci": { "ci": true, @@ -234,7 +216,6 @@ "0004-use-shell-wrapper.patch" ], "repo": "jimmy-park/openssl-cmake", - "tag": "%VERSION%", "version": "3.6.2" }, "opus": { @@ -250,8 +231,7 @@ "0002-no-install.patch" ], "repo": "xiph/opus", - "sha": "a3f0ec02b3", - "version": "1.5.2" + "version": "a3f0ec02b3" }, "quazip": { "hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8", @@ -264,16 +244,14 @@ ], "package": "QuaZip-Qt6", "repo": "stachenov/quazip", - "sha": "2e95c9001b", - "version": "1.5" + "version": "2e95c9001b" }, "sdl3": { "hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3", "min_version": "3.2.10", "package": "SDL3", "repo": "libsdl-org/SDL", - "tag": "release-%VERSION%", - "version": "3.4.8" + "version": "release-3.4.8" }, "sdl3-ci": { "ci": true, @@ -288,19 +266,16 @@ "hash": "b937c18a7b6277d77ca7ebfb216af4984810f77af4c32d101b7685369a4bd5eb61406223f82698e167e6311a728d07415ab59639fdf19eff71ad6dc2abfda989", "package": "SimpleIni", "repo": "brofield/simpleini", - "tag": "v%VERSION%", - "version": "4.25" + "version": "v4.25" }, "sirit": { - "artifact": "sirit-source-%VERSION%.tar.zst", "find_args": "CONFIG", + "hash": "b7cd6885acae3fc8698288d19febba0dd45e4a02a2b6563d2eb995a988d0847046e8d16a5dea7e0bf832b4321bcec134cdbafc553f6ede039e4cf34525c5dce5", "options": [ "SIRIT_USE_SYSTEM_SPIRV_HEADERS ON" ], "repo": "eden-emulator/sirit", - "tag": "v%VERSION%", - "version": "1.0.5", - "hash": "10b3ff60bdcad428bb4f54360ff749212333a1d24c0b3ed99e466b1bfcf99d2db6cf596c0f965854a2095dfef9b7ce4e045edb070fa9f76eb3b295ab03a4a293" + "version": "v1.0.5" }, "sirit-ci": { "ci": true, @@ -310,13 +285,13 @@ "version": "1.0.5" }, "spirv-headers": { - "hash": "cae8cd179c9013068876908fecc1d158168310ad6ac250398a41f0f5206ceff6469e2aaeab9c820bce9d1b08950c725c89c46e94b89a692be9805432cf749396", + "hash": "d624371dd455c66a300344c89812598ffe11b5eedba555779f789e85c29dc67317741858c60e0744a1e6755cc0d2759b8659f0674f4cc31479c4cb6fc25ed23b", "options": [ "SPIRV_WERROR OFF" ], "package": "SPIRV-Headers", "repo": "KhronosGroup/SPIRV-Headers", - "sha": "04f10f650d" + "version": "vulkan-sdk-1.4.341.0" }, "tzdb": { "artifact": "%VERSION%.tar.gz", @@ -324,7 +299,6 @@ "hash": "cce65a12bf90f4ead43b24a0b95dfad77ac3d9bfbaaf66c55e6701346e7a1e44ca5d2f23f47ee35ee02271eb1082bf1762af207aad9fb236f1c8476812d008ed", "package": "nx_tzdb", "repo": "eden-emu/tzdb_to_nx", - "tag": "%VERSION%", "version": "230326" }, "unordered-dense": { @@ -336,46 +310,40 @@ "0001-avoid-memset-when-clearing-an-empty-table.patch" ], "repo": "martinus/unordered_dense", - "sha": "7b55cab841", - "version": "4.8.1" + "version": "7b55cab841" }, "vulkan-headers": { "hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b", "min_version": "1.4.317", "package": "VulkanHeaders", "repo": "KhronosGroup/Vulkan-Headers", - "tag": "v%VERSION%", - "version": "1.4.345" + "version": "v1.4.345" }, "vulkan-memory-allocator": { "find_args": "CONFIG", "hash": "deb5902ef8db0e329fbd5f3f4385eb0e26bdd9f14f3a2334823fb3fe18f36bc5d235d620d6e5f6fe3551ec3ea7038638899db8778c09f6d5c278f5ff95c3344b", "package": "VulkanMemoryAllocator", "repo": "GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator", - "tag": "v%VERSION%", - "version": "3.3.0" + "version": "v3.3.0" }, "vulkan-utility-libraries": { "hash": "114f6b237a6dcba923ccc576befb5dea3f1c9b3a30de7dc741f234a831d1c2d52d8a224afb37dd57dffca67ac0df461eaaab6a5ab5e503b393f91c166680c3e1", "package": "VulkanUtilityLibraries", "repo": "KhronosGroup/Vulkan-Utility-Libraries", - "tag": "v%VERSION%", - "version": "1.4.345" + "version": "v1.4.345" }, "vulkan-validation-layers": { - "artifact": "android-binaries-%VERSION%.zip", + "artifact": "android-binaries-%NUMERIC_VERSION%.zip", "hash": "8812ae84cbe49e6a3418ade9c458d3be6d74a3dffd319d4502007b564d580998056e8190414368ec11b27bc83993c7a0dad713c31bcc3d9553b51243efee3753", - "package": "VVL", + "numeric_version": "1.4.341.0", "repo": "KhronosGroup/Vulkan-ValidationLayers", - "tag": "vulkan-sdk-%VERSION%", - "version": "1.4.341.0" + "version": "vulkan-sdk-%NUMERIC_VERSION%" }, "xbyak": { "hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a", "package": "xbyak", "repo": "herumi/xbyak", - "tag": "v%VERSION%", - "version": "7.35.2" + "version": "v7.35.2" }, "zlib": { "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", @@ -386,8 +354,7 @@ ], "package": "ZLIB", "repo": "madler/zlib", - "tag": "v%VERSION%", - "version": "1.3.2" + "version": "v1.3.2" }, "zstd": { "find_args": "MODULE", @@ -397,8 +364,7 @@ "ZSTD_BUILD_SHARED OFF" ], "repo": "facebook/zstd", - "sha": "b8d6101fba", "source_subdir": "build/cmake", - "version": "1.5.7" + "version": "b8d6101fba" } } diff --git a/docs/CPMUtil.md b/docs/CPMUtil.md index 52aa4aa722..8cebaaee42 100644 --- a/docs/CPMUtil.md +++ b/docs/CPMUtil.md @@ -8,6 +8,7 @@ CPMUtil is a wrapper around CPM that aims to reduce boilerplate and add useful u - [Common Properties](#common-properties) - [Standard Packages](#standard-packages) - [Versioning](#versioning) + - [Artifact Naming Errata](#artifact-naming-errata) - [Patches](#patches) - [Pre-built CI Packages](#pre-built-ci-packages) - [Usage](#usage) @@ -22,12 +23,18 @@ CPMUtil is a wrapper around CPM that aims to reduce boilerplate and add useful u - [Addendum: Module Path Packages](#addendum-module-path-packages) - [Example: OpenSSL](#example-openssl) - [Addendum: Adding Qt](#addendum-adding-qt) + - [Addendum: Package-Specific Overrides](#addendum-package-specific-overrides) + - [Addendum: Supply-Chain Security](#addendum-supply-chain-security) + - [Checksumming](#checksumming) + - [Caching](#caching) + - [Immutable Commit Hashes](#immutable-commit-hashes) ## Global Options -- `CPMUTIL_FORCE_SYSTEM` (default `OFF`): Require all CPM dependencies to use system packages. NOT RECOMMENDED! - - You may optionally override this (section) +- `CPMUTIL_FORCE_SYSTEM` (default `OFF`): Require all CPM dependencies to use system packages. + - You may optionally override this for each package: [Package-Specific Overrides](#addendum-package-specific-overrides) - `CPMUTIL_FORCE_BUNDLED` (default `ON` on MSVC and Android, `OFF` elsewhere): Require all CPM dependencies to use bundled packages. + - You may optionally override this for each package: [Package-Specific Overrides](#addendum-package-specific-overrides) - `CPMUTIL_PATCH_DIR` (default `${PROJECT_SOURCE_DIR}/.patch`): Path to patches used in packages. Stored as `/json-package-name/0001-patch-name.patch`, etc. - `CPM_SOURCE_CACHE` (default `${PROJECT_SOURCE_DIR}/.cache/cpm`): Where downloaded dependencies get stored. @@ -50,23 +57,25 @@ And may optionally define other properties like: For instance: ```json -"fmt": { - "repo": "fmtlib/fmt", - "tag": "12.1.0", - "hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2", - "min_version": "8", - "options": [ - "FMT_TEST ON" - ], - "patches": [ - "0001-disable-reference-copy.patch" - ] +{ + "fmt": { + "repo": "fmtlib/fmt", + "version": "12.1.0", + "hash": "f0da8...23f2", + "min_version": "8", + "options": [ + "FMT_TEST ON" + ], + "patches": [ + "0001-disable-reference-copy.patch" + ] + } } ``` Calling `AddJsonPackage(fmt)`: -- Searches for a system package named `fmt` of version 8 or higher +- Searches for a system package named `fmt` of version 8 or higher (`find_package(fmt 8)`) - If found, uses the system package and caches it for future use - If not found: - Downloads fmt 12.1.0 from the GitHub Archive into `.cache/cpm/fmt/12.1.0` @@ -85,63 +94,82 @@ These JSON properties are used by standard and CI packages alike. - `package`: The package name used by `find_package` to check for the existence of a system package. - If unset, defaults to the JSON key - `repo`: The Git repository the package is stored in, if applicable. -- `version`: The version of the package to download. This is required. -- `min_version`: The minimum required version of the package, if a system package is desired. +- `version`: The version of the package. This is required. + - Generally, this should be a 10-wide Git commit hash or Git tag. + - Tags must be fully qualified and include prefixes and suffixes, e.g. `boost-1.88.0` - `git_host`: The Git host the package is stored in, if applicable. Defaults to `github.com`. ## Standard Packages Normal packages, like the prior `fmt` example, *must* also define: -- `hash`: The SHA512 hash of the downloaded artifact. CPMUtil generally computes this for you. -- A valid version/URL identifier: - - `url`: Download from a raw URL. - - `sha`: A short or fully-qualified Git commit sha. CPMUtil recommends using 10-character wide shas. - - `tag`: A Git tag. See [Versioning](#versioning) for its relation to `version`. - - `artifact`: A GitHub/Forgejo/Gitea release artifact (requires `tag`). See [Versioning](#versioning) for its relation to `tag` and `version`. +- `hash`: The SHA512 hash of the downloaded artifact. CPMUtil generally computes this for you--if not, use `tools/cpmutil.sh package hash ` +- `min_version`: The minimum required version of the package, if a system package is desired. + +And may optionally define: + +- `url`: Download from a raw URL. +- `artifact`: A GitHub/Forgejo/Gitea release artifact. Requires `repo` to be set and valid. +- `numeric_version`: Replaces `%NUMERIC_VERSION%` in artifact and version definitions; see [Artifact Naming Errata](#artifact-naming-errata). + +See [Versioning](#versioning) for version/artifact information. The following are optional to define: - `source_subdir`: A subdirectory containing the `CMakeLists.txt` to configure a project. Useful for projects like `zstd`. -- `bundled`: Force the usage of a bundled package. Useful for packages where the system package is broken or nonexistent; e.g. including external fragment shaders. +- `bundled`: Force the usage of a bundled package. Useful for packages where the system package is broken or nonexistent; e.g. external fragment shaders or data archives. + - Note that this will conflict with `CPMUTIL_FORCE_SYSTEM`; for this reason, when using non-library archives, it may be best to allow the user to download and extract the archive manually and specify a local directory to it. - `find_args`: Additional arguments passed to `find_package`, e.g. `MODULE` - `patches`: Array of in-tree patches to apply to the downloaded source code. See [#Patches](TODO). - `options`: Array of CMake options to apply before configuring the package, e.g. `"FMT_TEST ON"`. ### Versioning -When using tags or artifacts, it may be cumbersome to repeat the version multiple times; especially if it's constantly changing. For this purpose, `tag` and `artifact` both support basic version text replacement. +When fetching from a Git repository, there are generally three methods of versioning: -`tag` can use `%VERSION%` to have its version replaced with the `version` defined for the package, e.g. for OpenSSL; when downloading, `tag` will evaluate to `openssl-3.6.2`: +- Commit hashes +- Git tags +- Release artifacts -```json -"openssl": { - "repo": "openssl/openssl", - "version": "3.6.2", - "tag": "openssl-%VERSION%" -} -``` +When `repo` is set, `version` field can be set to any commitish value, including commit hashes or Git tags. In the case of artifacts, `version` must be a Git tag, and `artifact` must be set to a release artifact attached to that tag. Many repositories intentionally version the filenames of their release artifacts; for this purpose, CPMUtil allows you to implant the version number into the artifact name. To do so, add `%VERSION%` to the artifact name; CPMUtil will then automatically replace `%VERSION%` with the `version` field. This means that when changing versions, you only need to update the version, not the artifact! -`artifact` also supports `%VERSION%` replacement, and can also use `%TAG%` to be replaced by the computed tag. Take this Boost definition: +Take Boost as an example. The artifact for Boost 1.90.0 is `boost-1.90.0-cmake.tar.xz`, and the tag is `boost-1.90.0`. Thus, we can set the artifact to `%VERSION%-cmake.tar.xz`: ```json "boost": { "repo": "boostorg/boost", - "tag": "boost-%VERSION%", - "version": "1.90.0" + "version": "boost-1.90.0", + "artifact": "%VERSION%-cmake.tar.xz" } ``` -Boost's artifact for this version is stored in `boost-1.90.0-cmake.tar.xz`. Notice that the computed tag,`boost-1.90.0`, is in the name of the artifact! Thus, `artifact` can be either: +The artifact will then evaluate as `boost-1.90.0-cmake.tar.xz`. -- `boost-%VERSION%-cmake.tar.xz` -- Or, even simpler: `%TAG%-cmake.tar.xz` +### Artifact Naming Errata + +While `%VERSION%` replacement is generally good enough for well-packaged projects, occasionally there may be some problematic packages. Take, for instance, Vulkan Validation Layers: + +- Tag (`version`): `vulkan-sdk-1.4.341.0` +- Artifact: `android-binaries-1.4.341.0.zip` + +Attempting to add version replacement to the artifact definition **would not work here!** In this case, you must utilize the `numeric_version` field described earlier; we would set `numeric_version` to `1.4.341.0` and add `%NUMERIC_VERSION%` replacements into our artifact and version fields: + +```json +"vulkan-validation-layers": { + "artifact": "android-binaries-%NUMERIC_VERSION%.zip", + "repo": "KhronosGroup/Vulkan-ValidationLayers", + "version": "vulkan-sdk-%NUMERIC_VERSION%", + "numeric_version": "1.4.341.0" +}, +``` -Future updates need only change the `version` identifier, and the artifact and tag will automatically be updated! +`artifact` will thus evaluate to `android-binaries-1.4.341.0.zip`, and `version` to `vulkan-sdk-1.4.341.0`. CPMUtil's auto-updater will also account for this and only update `numeric_version`! ### Patches -CPMUtil is able to apply in-place source tree patches to downloaded packages. These are defined in JSON as an array of names, preferably using `git-format-patch`'s scheme of `<4 digit number>-patch-name.patch`. These are stored in `/` (remember that `CPMUTIL_PATCH_DIR` defaults to `$ROOT/.patch`); e.g. `boost` patches would be in `.patch/boost`. Let's say we've made three patches and want to add them; in the Boost JSON definition, we would add: +CPMUtil is able to apply in-place source tree patches to downloaded packages. These are defined in JSON as an array of names, preferably using `git-format-patch`'s scheme of `<4 digit number>-patch-name.patch`. + +They are stored in `/` (remember that `CPMUTIL_PATCH_DIR` defaults to `$ROOT/.patch`); e.g. `boost` patches would be in `.patch/boost`. Let's say we've made three patches and want to add them; in the Boost JSON definition, we would add: ```json "patches": [ @@ -151,7 +179,7 @@ CPMUtil is able to apply in-place source tree patches to downloaded packages. Th ] ``` -Then, when Boost is downloaded, it will apply these patches in order to the source tree. +Then, when Boost is downloaded, it will apply these patches to the source tree in the order they are defined (compound/dependent patches are okay!). Note that when you add, remove, or modify patches, CPMUtil will invalidate your downloaded cache and re-fetch the source. To learn how to make patches, see [Addendum: Making Patches](#addendum-making-patches). @@ -199,12 +227,9 @@ If you're only concerned with basic usage, you can stop reading. For more advanc CPMUtil stores downloaded packages within `.cache/cpm` by default (see `CPM_SOURCE_CACHE`). Subdirectories stored within are lowercase representations of the `find_package` name for the package; for instance, a `vulkan-headers` definition with `package: "VulkanHeaders"` would be stored in `.cache/cpm/vulkanheaders`. -Within these subdirectories, additional directories are created for each individual version: - -- A four-character shorthand of `sha`, if defined -- If `sha` is not defined, the fully qualified `version` is used +Within these subdirectories, additional directories are created for each individual version, corresponding directly to their `version` field. CI packages use `--` unconditionally. -CI packages use `--` unconditionally. +To see the cache directory for a given package, use `tools/cpmutil.sh package dir `. ## Addendum: Making Patches @@ -242,7 +267,7 @@ If you are packaging a project that uses CPMUtil, read this! For sandboxed environments (e.g. Gentoo, nixOS) you must install all dependencies to the system beforehand and set `-DCPMUTIL_FORCE_SYSTEM=ON`. If a dependency is missing, get creating! -Alternatively, if CPMUtil pulls in a package that has no suitable way to install or use a system version, download it separately and pass `-DPackageName_DIR=/path/to/downloaded/dir` (e.g. shaders) +Alternatively, if CPMUtil pulls in a package that has no suitable way to install or use a system version, download it separately and pass `-D_CUSTOM_DIR=/path/to/downloaded/dir`. ### Unsandboxed @@ -263,14 +288,12 @@ Using the prior Vulkan example: "repo": "KhronosGroup/Vulkan-Headers", "package": "VulkanHeaders", "min_version": "1.4.317", - "version": "1.4.342", - "tag": "v%VERSION%" + "version": "v1.4.342" }, "vulkan-utility-libraries": { "repo": "KhronosGroup/Vulkan-Utility-Libraries", "package": "VulkanUtilityLibraries", - "version": "1.4.342", - "tag": "v%VERSION%" + "version": "v1.4.342" } ``` @@ -318,3 +341,32 @@ AddQt(QDash-CI/Qt 6.11.1) ``` Then, call `find_package(Qt6 ...)` and it will pull Qt from your downloaded source. + +## Addendum: Package-Specific Overrides + +There are three variables that CPMUtil defines for each package; these can be overriden by the user or in your CMake. `package` refers either to the `package` value in the JSON, or the package's JSON key if unset (see `package` in [Common Properties](#common-properties)): + +- `_FORCE_BUNDLED`: Forcefully bundle the package. This has the same effect as `CPMUTIL_FORCE_BUNDLED`, but only for this package. +- `_FORCE_BUNDLED`: Forcefully use the system package, failing if it can't be found. This has the same effect as `SYSTEM`, but only for this package. +- `_CUSTOM_DIR`: Path to an extracted copy of the package. CPMUtil will not attempt to download the package and will instead use the custom directory. + - For an example, see [CPMUtil's test case](https://git.crueter.xyz/CMake/CPMUtil/src/branch/master/tests/dir/CMakeLists.txt) + +Additionally, in CMake, you can add `FORCE_BUNDLED_PACKAGE ON` to your `AddJsonPackage` command--note that you will have to use the `NAME ` syntax as described in [Module Path Packages](#addendum-module-path-packages). This will overrule *all* other overrides, including `CPMUTIL_FORCE_SYSTEM` and `_FORCE_SYSTEM`--use with caution! + +## Addendum: Supply-Chain Security + +Many package managers suffer from the issue of supply chain security, specifically in regards to silent overwrites of existing packages or archives, e.g. tag sliding and artifact overwriting. CPMUtil has three methods to protect against this. + +### Checksumming + +CPMUtil *requires* SHA512 checksums for standard packages, and soon will for CI packages as well. If an attacker or compromised account slides a tag, overwrites a release artifact, or otherwise attempts to compromise anything that CPMUtil may fetch, **CPMUtil will not allow the download to continue!** This means that consumers of your build system can **only** download an artifact if the contents of the artifact are *exactly* indentical to what it was when it was configured--any changes at all will be rejected by CPMUtil. + +### Caching + +CPMUtil uses a mutable cache system, stored by default in `.cache/cpm`. Dependencies are downloaded and extracted here, and can be reused infinitely. This means that, for instance, if a package is compromised but you already have a cached local copy, you won't have to worry at all! + +### Immutable Commit Hashes + +CPMUtil is capable of using immutable Git commit hashes for its artifacts. These are (barring SHA1 collisions) completely immune to supply chain attacks--that is, unless the entire root server gets compromised to serve infected artifacts/source code; at which point there are much larger issues to worry about. This means that once you set a package to use a Git commit hash for its version, **it will stay the same forever**. This is useful if you want to ensure that consumers are never faced with download failures stemming from hash mismatches in case of compromised artifacts. + +Do note, however, that this will render the package incompatible with CPMUtil's built-in auto-updater, so you will have to manually update the package. diff --git a/tools/cpm/cmake/README.md b/tools/cpm/cmake/README.md new file mode 100755 index 0000000000..2dd6dfcbe6 --- /dev/null +++ b/tools/cpm/cmake/README.md @@ -0,0 +1,3 @@ +# CPMUtil CMake Test Scripts + +Testing out some CMake scripting functionality. diff --git a/tools/cpm/cmake/ScriptUtils.cmake b/tools/cpm/cmake/ScriptUtils.cmake new file mode 100755 index 0000000000..dbd908a1ea --- /dev/null +++ b/tools/cpm/cmake/ScriptUtils.cmake @@ -0,0 +1,341 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +# TODO: Account for CPMConfig.cmake +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMakeModules) +set(CPMUTIL_ROOT ${CMAKE_SOURCE_DIR}) + +include(CPMUtil) + +# Parse the JSON object for a given key. +macro(parse_key key) + get_json_object(${key}) + set(JSON_NAME ${key}) + parse_object(${object}) +endmacro() + +# Get a package's effective URL, for an already parsed object +function(get_package_url_object out) + if (${url}) + set(${out} "${url}") + else() + get_package_url(URL_OUT "${out}" + GIT_HOST "${git_host}" + REPO "${repo}" + VERSION "${version}" + ARTIFACT "${artifact}" + PACKAGE "${package}") + endif() + + return(PROPAGATE ${out}) +endfunction() + +# Fetch a package from an already-parsed object. +function(fetch_package_object) + set(optionArgs FORCE) + cmake_parse_arguments(ARG "${optionArgs}" "" "" ${ARGN}) + + if (${url}) + set(pkg_url "${url}") + else() + get_package_url(URL_OUT pkg_url + GIT_HOST "${git_host}" + REPO "${repo}" + VERSION "${version}" + ARTIFACT "${artifact}" + PACKAGE "${package}") + endif() + + if (DEFINED CACHE_PATH_OVERRIDE) + set(cache_path ${CACHE_PATH_OVERRIDE}) + else() + get_cache_path(${package} ${version} cache_path) + endif() + + set(fetch_args + URL "${pkg_url}" + HASH "${hash}" + PATH "${cache_path}" + PATCHES ${patches}) + + if(ARG_FORCE) + list(APPEND fetch_args FORCE) + endif() + + fetch_package(${fetch_args}) +endfunction() + +# Format the cpmfile. Requires one of: jq, python, perl +# If you don't have any of those, sorry not sorry. +# Maybe I should make a shell-based alternative. +function(format_cpmfile) + # jq is the preferred formatter since it's the fastest + cpm_find_program(JQ_EXECUTABLE jq) + if (JQ_EXECUTABLE) + set(command ${JQ_EXECUTABLE} --indent 4 -S .) + else() + # Python is simple and works + find_package(Python 3.5 COMPONENTS Interpreter QUIET) + if (Python_FOUND) + set(command ${Python_EXECUTABLE} -m json.tool + --indent 4 --sort-keys) + else() + # json_pp (part of perl) also works well + cpm_find_program(JSONPP_EXECUTABLE json_pp) + if (JSONPP_EXECUTABLE) + set(json_opts "indent" "indent_length=4" "canonical" + "space_after=1" "space_before=0") + string(JOIN "," json_opts_str ${json_opts}) + + set(command ${JSONPP_EXECUTABLE} -f json -t json -json_opt + "${json_opts_str}") + else() + fatal("Fatal: could not find one of jq, Python, or perl" + "(json_pp). Install one of these packages to use" + "CPMUtil's tooling. If they ARE installed, your" + "CMake installation is broken.") + endif() + endif() + endif() + + get_cpmfile_path(file) + mktempdir(TMP) + set(tmp_file ${TMP}/cpmfile.json) + + execute_process(COMMAND ${command} + INPUT_FILE ${file} + OUTPUT_FILE ${tmp_file}) + + # TODO: error handling, mv, cp? + file(COPY_FILE ${tmp_file} ${file}) + + file(REMOVE_RECURSE ${TMP}) +endfunction() + +# Computes expected SHA512 hash of a package +function(get_package_hash url out) + mktempdir(TMP) + + get_filename_component(filename ${url} NAME) + set(file ${TMP}/${filename}) + + cpm_download("${url}" "${file}") + file(SHA512 ${file} ${out}) + + file(REMOVE_RECURSE ${TMP}) + return(PROPAGATE ${out}) +endfunction() + +# Download and put the content into a variable. +function(cpm_download_var url out) + mktempdir(TMP) + + set(file ${TMP}/tmp) + cpm_download("${url}" "${file}") + file(READ ${file} ${out}) + + file(REMOVE_RECURSE ${TMP}) + return(PROPAGATE ${out}) +endfunction() + +# Check if a URL request succeeds without actually saving anything +function(cpm_url_exists url out) + foreach(i RANGE 5) + file(DOWNLOAD "${url}" STATUS ret LOG log TIMEOUT 10) + list(GET ret 0 code) + + if (code EQUAL 0) + set(${out} TRUE) + break() + else() + if (log MATCHES "HTTP/[0-9.]+ (429|403)") + sleep(5) + continue() + endif() + + set(${out} FALSE) + break() + endif() + endforeach() + + return(PROPAGATE ${out}) +endfunction() + +# Get latest tag for a package. +# Requires an already-parsed object +function(get_latest_tag out) + # TODO: Ci packages + if (NOT repo OR ci) + set(${out} null) + return(PROPAGATE ${out}) + endif() + + # first determine if this is a tag or not + if (git_host STREQUAL github.com) + set(api "https://api.github.com/repos/${repo}") + set(check_endpoint "/git/refs/tags") + else() + set(api "https://${git_host}/api/v1/repos/${repo}") + set(check_endpoint "/tags") + endif() + + # artifacts must only check releases + if ("${artifact}" STREQUAL "") + set(json_key name) + set(endpoint "/tags") + else() + set(json_key tag_name) + set(endpoint "/releases") + set(check_endpoint "/releases/tags") + endif() + + cpm_url_exists("${api}${check_endpoint}/${version}" is_tag) + + if (NOT is_tag) + set(${out} null) + return(PROPAGATE ${out}) + endif() + + # strip out prefixes e.g. boost-, openssl-, v + # NOTE: subrels e.g. -1 at the end may cause issues. + string(REGEX REPLACE "[^0-9.-]" "" t_numeric_version "${version}") + string(REGEX REPLACE "-$|^-" "" t_numeric_version "${numeric_version}") + + # now api req + cpm_download_var("${api}${endpoint}" tags) + + string(JSON len LENGTH ${tags}) + math(EXPR last_index "${len} - 1") + + set(greatest_version_numeric ${t_numeric_version}) + set(greatest_version ${version}) + + foreach(i RANGE ${last_index}) + string(JSON tag_obj GET "${tags}" ${i}) + string(JSON tag_name GET "${tag_obj}" ${json_key}) + + # same as above + string(REGEX REPLACE "[^0-9.-]" "" numeric_tag "${tag_name}") + string(REGEX REPLACE "-$|^-" "" numeric_tag "${numeric_tag}") + + if (numeric_tag VERSION_GREATER_EQUAL greatest_version_numeric) + set(greatest_version_numeric ${numeric_tag}) + set(greatest_version ${tag_name}) + endif() + endforeach() + + # numeric version replacement + if (numeric_version) + set(${out} ${greatest_version_numeric}) + else() + set(${out} ${greatest_version}) + endif() + + return(PROPAGATE ${out}) +endfunction() + +# TODO(crueter): Combine these + +# Update hash and version of a package +# Outputs the updated object +function(modify_package object version hash out) + string(JSON new_object SET "${object}" hash "\"${hash}\"") + string(JSON new_object SET "${new_object}" version "\"${version}\"") + + set(${out} "${new_object}") + return(PROPAGATE ${out}) +endfunction() + +# Update hash and numeric_version of a package +# Outputs the updated object +function(modify_package_numeric object version hash out) + string(JSON new_object SET "${object}" hash "\"${hash}\"") + string(JSON new_object SET "${new_object}" numeric_version "\"${version}\"") + + set(${out} "${new_object}") + return(PROPAGATE ${out}) +endfunction() + + +function(get_cpmfile_keys out) + get_cpmfile_content(object) + + string(JSON len LENGTH ${object}) + + math(EXPR last_index "${len} - 1") + foreach(i RANGE ${last_index}) + string(JSON key MEMBER ${object} ${i}) + list(APPEND ${out} ${key}) + endforeach() + + return(PROPAGATE ${out}) +endfunction() + +function(parse_script_args out) + if(PRINT_USAGE) + usage() + cmake_language(EXIT 0) + endif() + + if(NOT CMAKE_SCRIPT_MODE_FILE OR NOT CMAKE_ARGC) + set(${out} "" PARENT_SCOPE) + return() + endif() + + set(found_script FALSE) + set(idx 0) + + get_filename_component(script_name "${CMAKE_SCRIPT_MODE_FILE}" NAME) + + while(idx LESS CMAKE_ARGC) + if(found_script) + set(arg "${CMAKE_ARGV${idx}}") + if(NOT arg STREQUAL "--") + list(APPEND positional_args ${arg}) + endif() + elseif(CMAKE_ARGV${idx} STREQUAL CMAKE_SCRIPT_MODE_FILE) + set(found_script TRUE) + else() + get_filename_component(arg_name "${CMAKE_ARGV${idx}}" NAME) + if(arg_name STREQUAL script_name) + set(found_script TRUE) + endif() + endif() + math(EXPR idx "${idx} + 1") + endwhile() + + if(ALL_PACKAGES AND NOT NO_ALL) + get_cpmfile_keys(all_keys) + if(NO_CI) + set(filtered_keys) + foreach(key ${all_keys}) + parse_key(${key}) + if(NOT ci) + list(APPEND filtered_keys ${key}) + endif() + endforeach() + set(positional_args ${filtered_keys}) + else() + set(positional_args ${all_keys}) + endif() + endif() + + set(${out} ${positional_args} PARENT_SCOPE) +endfunction() + +# Convert a CMake list to a JSON array +function(list_to_array list out) + set(${out} "[]") + set(idx 0) + foreach(elem ${list}) + string(JSON ${out} SET ${${out}} "${idx}" "\"${elem}\"") + math(EXPR idx "${idx} + 1") + endforeach() + + return(PROPAGATE ${out}) +endfunction() \ No newline at end of file diff --git a/tools/cpm/format.sh b/tools/cpm/cmake/format.cmake similarity index 53% rename from tools/cpm/format.sh rename to tools/cpm/cmake/format.cmake index c1452c4cb2..7eff47655f 100755 --- a/tools/cpm/format.sh +++ b/tools/cpm/cmake/format.cmake @@ -1,9 +1,11 @@ -#!/bin/sh -e +#!/usr/bin/env -S cmake -P # SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later -jq --indent 4 -S cpmfile.json.new -mv cpmfile.json.new cpmfile.json +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/ScriptUtils.cmake) # TODO: Run some sanity checks e.g. patches exist, etc. +format_cpmfile() diff --git a/tools/cpm/cmake/ls.cmake b/tools/cpm/cmake/ls.cmake new file mode 100755 index 0000000000..41a1d1d7af --- /dev/null +++ b/tools/cpm/cmake/ls.cmake @@ -0,0 +1,24 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/ScriptUtils.cmake) + +set(NO_ALL TRUE) + +function(usage) + echo([=[ +Usage: cpmutil.sh ls + +List all packages in the cpmfile. +]=]) +endfunction() + +get_cpmfile_keys(keys) + +foreach(key ${keys}) + echo("${key}") +endforeach() diff --git a/tools/cpm/cmake/package/add.cmake b/tools/cpm/cmake/package/add.cmake new file mode 100644 index 0000000000..02c4e1d900 --- /dev/null +++ b/tools/cpm/cmake/package/add.cmake @@ -0,0 +1,104 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: add.cmake [OPTIONS...] + +Internal use only. +]=]) +endfunction() + +if (NOT DEFINED KEY) + fatal("KEY is required") +endif() + +if (NOT DEFINED REPO) + fatal("REPO is required") +endif() + +if (NOT DEFINED VERSION) + fatal("VERSION is required") +endif() + + +if (NOT DEFINED CI) + set(CI FALSE) +endif() + +# construct json +set(object "{}") + +macro(add key val) + set(${key} ${val}) + string(JSON object SET ${object} ${key} "\"${val}\"") +endmacro() + +add(repo ${REPO}) +add(version ${VERSION}) + +if (DEFINED GIT_HOST AND NOT "${GIT_HOST}" STREQUAL github.com) + add(git_host ${GIT_HOST}) +else() + set(git_host github.com) +endif() + +if (DEFINED PACKAGE) + add(package ${PACKAGE}) +else() + set(package ${KEY}) +endif() + +if (DEFINED FIND_ARGS) + add(find_args "${FIND_ARGS}") +endif() + +if (DEFINED MIN_VERSION) + add(min_version ${MIN_VERSION}) +endif() + +if (DEFINED ARTIFACT) + add(artifact ${ARTIFACT}) +endif() + +if (CI) + add(ci true) + + if (DEFINED DISABLED_PLATFORMS) + list_to_array(${DISABLED_PLATFORMS} json_disabled) + string(JSON object SET "${object}" disabled_platforms "${json_disabled}") + endif() +else() + if (DEFINED OPTIONS) + list_to_array(${OPTIONS} json_options) + string(JSON object SET "${object}" options "${json_options}") + endif() + + # get hash + get_package_url_object(pkg_url) + get_package_hash("${pkg_url}" pkg_hash) + + add(hash ${pkg_hash}) + +endif() + +echo("\"${KEY}\": ${object}") + +# now write +get_cpmfile_content(cpmfile) + +# update cached cpmfile content +string(JSON cpmfile SET "${cpmfile}" "${KEY}" "${object}") + +# write cached cpmfile +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() + +echo("-- Added ${KEY}") diff --git a/tools/cpm/cmake/package/dir.cmake b/tools/cpm/cmake/package/dir.cmake new file mode 100755 index 0000000000..bbbf523b31 --- /dev/null +++ b/tools/cpm/cmake/package/dir.cmake @@ -0,0 +1,33 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package dir [-a|--all] [PACKAGE]... + +Get the local directory for the specified packages. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +parse_script_args(args) + +# TODO: CI packages. + +foreach(key ${args}) + parse_key(${key}) + + # Guh. + get_cache_path(${package} ${version} cache_path) + + cmake_path(ABSOLUTE_PATH cache_path NORMALIZE OUTPUT_VARIABLE abs_path) + echo("${key}: ${abs_path}") +endforeach() \ No newline at end of file diff --git a/tools/cpm/cmake/package/fetch.cmake b/tools/cpm/cmake/package/fetch.cmake new file mode 100755 index 0000000000..f2b678de01 --- /dev/null +++ b/tools/cpm/cmake/package/fetch.cmake @@ -0,0 +1,34 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package fetch [-a|--all] [PACKAGE]... + +Fetch the specified package or packages from their defined download locations. +If the package is already cached, it will not be re-fetched. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +set(NO_CI TRUE) +parse_script_args(args) + +foreach(key ${args}) + if (ci) + continue() + endif() + + parse_key(${key}) + echo("-- ${key}") + + fetch_package_object() +endforeach() diff --git a/tools/cpm/cmake/package/hash.cmake b/tools/cpm/cmake/package/hash.cmake new file mode 100755 index 0000000000..6be23c9882 --- /dev/null +++ b/tools/cpm/cmake/package/hash.cmake @@ -0,0 +1,59 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package hash [-a|--all] [PACKAGE]... + +Check the hash of a specific package or packages. +If a hash mismatch occurs, this script will update the package's hash. + +Options: + -a, --all Operate on all packages in this project. + +Note that this procedure will usually take a long time +depending on the number and size of dependencies. +]=]) +endfunction() + +set(NO_CI TRUE) +parse_script_args(args) + +get_cpmfile_content(cpmfile) +foreach(key ${args}) + if (ci) + continue() + endif() + + parse_key("${key}") + echo("-- ${key}") + + get_package_url_object(pkg_url) + get_package_hash("${pkg_url}" pkg_hash) + + if (pkg_hash STREQUAL hash) + echo("Hashes match") + else() + echo_error("Hash mismatch") + echo_error("Expected: ${hash}") + echo_error("Got: ${pkg_hash}") + + modify_package("${object}" "${version}" "${pkg_hash}" new_object) + + # update cached cpmfile content + string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}") + + echo("Corrected hash for ${key}") + endif() +endforeach() + +# write cached cpmfile +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() diff --git a/tools/cpm/cmake/package/patch.cmake b/tools/cpm/cmake/package/patch.cmake new file mode 100644 index 0000000000..3844921827 --- /dev/null +++ b/tools/cpm/cmake/package/patch.cmake @@ -0,0 +1,155 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package patch [PACKAGE] + +Create an in-tree patch for the specified package. +]=]) +endfunction() + +set(NO_ALL TRUE) + +# arg parsing +parse_script_args(args) + +list(LENGTH args arg_len) +if(arg_len GREATER 0) + list(GET args 0 KEY) +endif() + +if (NOT KEY) + fatal("You must provide a key") +endif() + +if (NOT DEFINED DESCRIPTION) + fatal("No description provided") +endif() + +parse_key(${KEY}) +get_cache_path(${package} ${version} local_cache) + +if (NOT EXISTS ${local_cache}) + fatal("${package} is not fetched locally") +endif() + +# get last patch number + 1 +list(LENGTH patches patches_len) +math(EXPR last_index "${patches_len} - 1") +list(GET patches ${last_index} patch) +string(REGEX MATCH "[0-9][0-9][0-9][0-9]" number "${patch}") +math(EXPR new_patchnum "${number} + 1") + +# now pad it for filename usage +set(padded "0000${new_patchnum}") +string(LENGTH ${padded} total_len) +math(EXPR start_index "${total_len} - 4") +string(SUBSTRING ${padded} ${start_index} 4 padded_patch_number) + +# this requires Git +find_package(Git REQUIRED) + +# fetch temporary package +mktempdir(TMP) + +set(CACHE_PATH_OVERRIDE ${TMP}/local) +fetch_package_object() + +# make patch dir +set(patch_dir "${CPMUTIL_PATCH_DIR}/${KEY}") +file(MAKE_DIRECTORY "${patch_dir}") + +# git stuff +macro(git_cmd out out_status) + echo("${GIT_EXECUTABLE} ${ARGN}") + # TODO: error handling + execute_process(COMMAND ${GIT_EXECUTABLE} ${ARGN} + WORKING_DIRECTORY ${CACHE_PATH_OVERRIDE} + OUTPUT_VARIABLE ${out} + RESULT_VARIABLE ${out_status}) +endmacro() + +# initialize +git_cmd(_ _ init) +git_cmd(_ _ add -A) +git_cmd(_ _ commit -m init) +git_cmd(_ _ "--work-tree=${local_cache}" add -A) + +# check for diffs +git_cmd(_ diff_status diff --cached --quiet) +git_cmd(out _ diff) + +if (diff_status EQUAL 0) + echo(${out}) + echo_error("No differences found between local copy and source") + file(REMOVE_RECURSE ${TMP}) + cmake_language(EXIT 1) +endif() + +# prompt for patch description +git_cmd(_ commit_status commit -m "${DESCRIPTION}") + +# format patch +git_cmd(patch_content _ format-patch -1 HEAD --stdout) + +# now get patch name... + +# strip out existing numeric prefix +string(REGEX REPLACE "^[0-9][0-9][0-9][0-9]-" "" name_part "${DESCRIPTION}") +# spaces to dashes +string(REPLACE " " "-" name_part "${name_part}") +# strip out non-alphanumeric or dash characters +string(REGEX REPLACE "[^a-zA-Z0-9-]" "" name_part "${name_part}") +# collapse consecutive dashes +string(REGEX REPLACE "-+" "-" name_part "${name_part}") +# strip leading/trailing dashes +string(REGEX REPLACE "^-|-$" "" name_part "${name_part}") + +if (NOT name_part) + set(name_part "patch") + echo_error("Warning: could not determine patch name") +endif() + +# Truncate to 49 chars (60 - num prefix - `.patch` suffix) +string(SUBSTRING "${name_part}" 0 49 name_part) + +# And remove any trailing dashes +string(REGEX REPLACE "-$" "" name_part "${name_part}") + +# now construct and save patch +set(patch_name "${padded_patch_number}-${name_part}.patch") + +file(WRITE "${patch_dir}/${patch_name}" "${patch_content}") + +echo("-- Patch created at ${patch_dir}/${patch_name}") +file(REMOVE_RECURSE ${TMP}) + +# Now add to cpmfile. +get_cpmfile_content(content) + +# Build new patches JSON array +set(new_patches "[") +if(patches) + string(JSON existing GET "${content}" "${KEY}" "patches") + string(JSON len LENGTH "${existing}") + math(EXPR range_end "${len} - 1") + foreach(idx RANGE ${range_end}) + string(JSON val GET "${existing}" ${idx}) + string(APPEND new_patches "\"${val}\",") + endforeach() +endif() +string(APPEND new_patches "\"${patch_name}\"]") + +# Update the cpmfile +string(JSON content SET "${content}" "${KEY}" patches "${new_patches}") +get_cpmfile_path(file_path) +file(WRITE "${file_path}" "${content}") +format_cpmfile() + diff --git a/tools/cpm/cmake/package/reset.cmake b/tools/cpm/cmake/package/reset.cmake new file mode 100755 index 0000000000..2b210e4f64 --- /dev/null +++ b/tools/cpm/cmake/package/reset.cmake @@ -0,0 +1,34 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package reset [-a|--all] [PACKAGE]... + +Reset a locally fetched package to its original state. +This is most useful for dropping any changes you've made. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +set(NO_CI TRUE) +parse_script_args(args) + +foreach(key ${args}) + if (ci) + continue() + endif() + + parse_key(${key}) + echo("-- ${key}") + + fetch_package_object(FORCE) +endforeach() diff --git a/tools/cpm/cmake/package/rm.cmake b/tools/cpm/cmake/package/rm.cmake new file mode 100755 index 0000000000..2547758920 --- /dev/null +++ b/tools/cpm/cmake/package/rm.cmake @@ -0,0 +1,31 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package rm [PACKAGE]... + +Delete a package or packages' cpmfile definition. +]=]) +endfunction() + +set(NO_ALL TRUE) +parse_script_args(args) + +get_cpmfile_content(object) + +# Remove key +foreach(key ${args}) + string(JSON object REMOVE "${object}" ${key}) +endforeach() + +# write +get_cpmfile_path(file) +file(WRITE ${file} "${object}") +format_cpmfile() \ No newline at end of file diff --git a/tools/cpm/cmake/package/update.cmake b/tools/cpm/cmake/package/update.cmake new file mode 100644 index 0000000000..82ea70340a --- /dev/null +++ b/tools/cpm/cmake/package/update.cmake @@ -0,0 +1,97 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package update [-a|--all] [-c|--commit] [PACKAGE]... + +Check for updates for a package or packages. + +Options: + -a, --all Operate on all packages in this project. + -c, --commit Automatically generate a commit message +]=]) +endfunction() + +set(NO_CI TRUE) + +parse_script_args(args) + +get_cpmfile_content(cpmfile) +set(update_log "") +set(changed FALSE) + +foreach(key ${args}) + parse_key(${key}) + + get_latest_tag(tag) + + if (${tag} STREQUAL null) + continue() + elseif(NOT ${tag} STREQUAL ${version}) + if (numeric_version) + set(old_version ${numeric_version}) + echo("${key}: ${old_version} -> ${tag}") + set(numeric_version ${tag}) + get_json_element("${object}" version version) + else() + set(old_version ${version}) + echo("${key}: ${old_version} -> ${tag}") + set(version ${tag}) + endif() + + get_json_element("${object}" artifact artifact) + + # Redo version replacements + process_version_replacements() + + get_package_url_object(pkg_url) + get_package_hash("${pkg_url}" pkg_hash) + + if (numeric_version) + modify_package_numeric("${object}" + "${tag}" "${pkg_hash}" new_object) + else() + modify_package("${object}" "${tag}" "${pkg_hash}" new_object) + endif() + + # update cached cpmfile content + string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}") + + # used for commit + string(APPEND update_log "* ${key}: ${old_version} -> ${tag}\n") + set(changed TRUE) + else() + echo("${key}: Up to date") + endif() +endforeach() + +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() + +if(MAKE_COMMIT AND changed) + mktempdir(TMP) + + find_package(Git QUIET) + + if (NOT Git_FOUND) + fatal("Git is required to be installed for --commit," + "but it could not be found.") + endif() + + set(msg_file ${TMP}/commit_msg.txt) + file(WRITE ${msg_file} "Update dependencies\n\n${update_log}") + execute_process(COMMAND ${GIT_EXECUTABLE} add cpmfile.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + execute_process(COMMAND ${GIT_EXECUTABLE} commit -F ${msg_file} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + + file(REMOVE_RECURSE ${TMP}) +endif() diff --git a/tools/cpm/cmake/package/url.cmake b/tools/cpm/cmake/package/url.cmake new file mode 100755 index 0000000000..cfc1d41037 --- /dev/null +++ b/tools/cpm/cmake/package/url.cmake @@ -0,0 +1,28 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package url [-a|--all] [PACKAGE]... + +Get the download URL for the specified packages. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +parse_script_args(args) + +foreach(key ${args}) + parse_key(${key}) + + get_package_url_object(pkg_url) + echo("${key}: ${pkg_url}") +endforeach() \ No newline at end of file diff --git a/tools/cpm/cmake/package/version.cmake b/tools/cpm/cmake/package/version.cmake new file mode 100755 index 0000000000..6ce42a06cd --- /dev/null +++ b/tools/cpm/cmake/package/version.cmake @@ -0,0 +1,75 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package version [PACKAGE] [VERSION] + +Update a package's version. If the package uses a sha, you must provide a sha, +and if the package uses a tag, you must provide the fully qualified tag. +]=]) +endfunction() + +set(NO_ALL TRUE) +# arg parsing +parse_script_args(args) + +list(LENGTH args arg_len) +if(arg_len GREATER 0) + list(GET args 0 KEY) +endif() + +if(arg_len GREATER 1) + list(GET args 1 NEW_VERSION) +endif() + +# checks +if (NOT KEY) + fatal("You must provide a key") +endif() + +if (NOT NEW_VERSION) + fatal("You must provide a version") +endif() + +# action +get_cpmfile_content(cpmfile) +parse_key("${KEY}") + +if (numeric_version) + set(numeric_version ${NEW_VERSION}) + get_json_element("${object}" version version) +else() + set(version ${NEW_VERSION}) +endif() + +get_json_element("${object}" artifact artifact) + +# Redo version replacements +process_version_replacements() + +get_package_url_object(pkg_url) +get_package_hash("${pkg_url}" pkg_hash) + +if (numeric_version) + modify_package_numeric("${object}" + "${NEW_VERSION}" "${pkg_hash}" new_object) +else() + modify_package("${object}" "${NEW_VERSION}" "${pkg_hash}" new_object) +endif() + +# update cached cpmfile content +string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}") + +# write cached cpmfile +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() + +echo("-- * Updated") diff --git a/tools/cpm/cmake/package/which.cmake b/tools/cpm/cmake/package/which.cmake new file mode 100755 index 0000000000..46c5dcc021 --- /dev/null +++ b/tools/cpm/cmake/package/which.cmake @@ -0,0 +1,36 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package which [PACKAGE]... + +Check if a package or packages are defined in the cpmfile. +]=]) +endfunction() + +set(NO_ALL TRUE) +parse_script_args(args) +set(exit 0) + +get_cpmfile_content(object) + +foreach(key ${args}) + # Check if a key exists + string(JSON member ERROR_VARIABLE err GET "${object}" ${key}) + + if (NOT err) + echo("${key}") + else() + echo_error("${key} not defined in cpmfile") + set(exit 1) + endif() +endforeach() + +cmake_language(EXIT ${exit}) \ No newline at end of file diff --git a/tools/cpm/cmake/update.cmake b/tools/cpm/cmake/update.cmake new file mode 100755 index 0000000000..79c1fa1d00 --- /dev/null +++ b/tools/cpm/cmake/update.cmake @@ -0,0 +1,33 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/ScriptUtils.cmake) + +# Update CPMUtil and its tooling/etc +set(pwd ${CMAKE_SOURCE_DIR}) + +set(host "https://git.crueter.xyz") +set(repo "CMake/CPMUtil") +set(release "releases/download/continuous") +set(filename CPMUtil.tar.zst) + +mktempdir(TMP) + +# Download tarball +set(url "${host}/${repo}/${release}/${filename}") +set(file ${TMP}/${filename}) + +cpm_download(${url} ${file}) + +# Extract to current working directory +file(ARCHIVE_EXTRACT + INPUT ${file} + DESTINATION ${pwd}) + +# done :) +echo("Updated CPMUtil") +file(REMOVE_RECURSE ${TMP}) \ No newline at end of file diff --git a/tools/cpm/common.sh b/tools/cpm/common.sh deleted file mode 100755 index 09da6358be..0000000000 --- a/tools/cpm/common.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -: "${CPM_SOURCE_CACHE:=$PWD/.cache/cpm}" -: "${CPMUTIL_PATCH_DIR:=$PWD/.patch}" - -# TODO: cache cpmfile defs? - -cmd_exists() { - command -v "$1" >/dev/null 2>&1 -} - -must_install() { - for cmd in "$@"; do - cmd_exists "$cmd" || { echo "-- $cmd must be installed" && exit 1; } - done -} - -# Random integer between 100000 and 999999 -_randint() { - awk 'BEGIN { srand(); print int(100000 + rand() * 900000) }' -} - -# Use mktemp if available, use a local temp dir otherwise -make_temp_dir() { - if cmd_exists mktemp; then - mktemp -d - else - TMP="$PWD/.cpm/tmp-$(_randint)" - mkdir -p "$TMP" - echo "$TMP" - fi -} - -# must_install jq find mktemp tar 7z unzip sha512sum git patch curl - -if [ ! -s cpmfile.json ]; then - # TODO: actually make it a no-op - echo "-- Warning: cpmfile.json does not exist or is empty, most commands will be no-ops" -else - LIBS=$(jq -j 'keys_unsorted | join(" ")' cpmfile.json) - export LIBS -fi diff --git a/tools/cpm/migrate.sh b/tools/cpm/migrate.sh index 8be2b1786c..fd4d6880db 100755 --- a/tools/cpm/migrate.sh +++ b/tools/cpm/migrate.sh @@ -6,14 +6,22 @@ SUBMODULES="$(git submodule status --recursive | cut -c2-)" : "${SUBMODULES:?No submodules defined!}" -tmp=$(mktemp) -printf '{}' >"$tmp" - IFS=" " for i in $SUBMODULES; do - sha=$(echo "$i" | cut -d" " -f1 | cut -c1-10) - ver=$(echo "$i" | cut -d" " -f3 | tr -d '()') + commit=$(echo "$i" -d" " -f1 | cut -c1-10) + short_commit=$(echo "$i" -d" " -f1 | cut -c1-7) + + ref=$(echo "$i" | cut -d" " -f3 | tr -d '()') + + case "$ref" in + # ref == commit, use commit versioning + "$short_commit") version="$commit" ;; + # ref is a branch, use commit versioning + heads/*) version="$commit" ;; + # ref is (probably) a tag, use tag versioning + *) version="$ref" ;; + esac path=$(echo "$i" | cut -d" " -f2) name=$(echo "$path" | awk -F/ '{print $NF}') @@ -21,26 +29,9 @@ for i in $SUBMODULES; do remote=$(git -C "$path" remote get-url origin) host=$(echo "$remote" | cut -d"/" -f3) - [ "$host" != github.com ] || host= repo=$(echo "$remote" | cut -d"/" -f4-5 | cut -d'.' -f1) - entry=$(jq -n --arg name "$name" \ - --arg sha "$sha" \ - --arg ver "$ver" \ - --arg repo "$repo" \ - --arg host "$host" \ - '{ - ($name): { - sha: $sha, - version: $ver, - repo: $repo - } + (if $host != "" then {git_host: $host} else {} end) - }') - - jq --argjson new "$entry" '. + $new' "$tmp" >"${tmp}.new" - mv "$tmp.new" "$tmp" + cmake -DKEY="$name" -DVERSION="$version" -DREPO="$repo" -DGIT_HOST="$host" \ + -P "$CMAKE/package/add.cmake" done - -jq '.' "$tmp" >cpmfile.json -rm -f "$tmp" diff --git a/tools/cpm/package.sh b/tools/cpm/package.sh index bf2f3feaa7..8246222d50 100755 --- a/tools/cpm/package.sh +++ b/tools/cpm/package.sh @@ -12,17 +12,17 @@ Usage: cpmutil.sh package [command] Operate on a package or packages. Commands: - hash Verify the hash of a package, and update it if needed - update Check for updates for a package - fetch Fetch a package and place it in the cache - add Add a new package - rm Remove a package - version Change the version of a package - which Check if a package is defined - download Get the download URL for a package - dir Get the local directory for a package - reset Reset a fetched package to its original state - patch Create an in-tree patch based on local modifications + hash Verify the hash of a package, and update it if needed + update Check for updates for a package + fetch Fetch a package and place it in the cache + add Add a new package + rm Remove a package + version Change the version of a package + which Check if a package is defined + url Get the download URL for a package + dir Get the local directory for a package + reset Reset a fetched package to its original state + patch Add a patch to a package in the cpmfile EOF @@ -30,11 +30,48 @@ EOF } SCRIPTS=$(CDPATH='' cd -- "$(dirname -- "$0")/package" && pwd) -export SCRIPTS +CMAKE="$CMAKE/package" + +export SCRIPTS CMAKE + +filter_args() { + print_usage=false + all_flag=false + commit_flag=false + filtered="" + + for arg in "$@"; do + case "$arg" in + -h | --help) print_usage=true ;; + -a | --all) all_flag=true ;; + -c | --commit) commit_flag=true ;; + -ac | -ca) + commit_flag=true + all_flag=true + ;; + *) filtered="$filtered $arg" ;; + esac + done +} while :; do case "$1" in - hash | update | fetch | add | rm | version | which | download | reset | patch | dir) + dir | fetch | hash | reset | rm | url | version | which | update) + cmd="$1" + shift + + filter_args "$@" + cmake_defines="" + + if $print_usage; then cmake_defines="$cmake_defines -DPRINT_USAGE=TRUE"; fi + if $all_flag; then cmake_defines="$cmake_defines -DALL_PACKAGES=TRUE"; fi + if $commit_flag; then cmake_defines="$cmake_defines -DMAKE_COMMIT=TRUE"; fi + + # shellcheck disable=SC2086 + cmake $cmake_defines -P "$CMAKE/$cmd.cmake" -- $filtered + break + ;; + add | patch) cmd="$1" shift "$SCRIPTS/$cmd".sh "$@" diff --git a/tools/cpm/package/add.sh b/tools/cpm/package/add.sh index 8531f524fb..0129613931 100755 --- a/tools/cpm/package/add.sh +++ b/tools/cpm/package/add.sh @@ -49,6 +49,153 @@ done [ -n "$PKG" ] || die "You must specify a package name." -export PKG +# This reads a single-line input from the user and also gives them +# help if needed. +# $1: The prompt itself, without any trailing spaces or whatever +# $2: The help text that gets shown when the user types a question mark +# $3: This is set to "required" if it's necessary, +# otherwise it can continue without input. +# Stores its output in the "reply" variable +read_single() { + while :; do + printf -- "-- %s" "$1" + [ -z "$2" ] || printf " (? for help, %s)" "$3" + printf ": " + if ! IFS= read -r reply; then + echo + [ "$3" = "required" ] && continue || reply="" + fi + case "$reply" in + "?") echo "$2" ;; + "") [ "$3" = "required" ] && continue || return 0 ;; + *) return 0 ;; + esac + done +} + +# read_single, but optional +optional() { + read_single "$1" "$2" "optional" +} + +# a +required() { + read_single "$1" "$2" "required" +} + +# Basically the same as the single line function except multiline, +# also it's never "required" so we don't need that handling. +multi() { + echo "-- $1" + if [ -n "$2" ]; then + echo "-- (? on first line for help, Ctrl-D to finish)" + else + echo "-- (Ctrl-D to finish)" + fi + while :; do + reply=$(cat) + if [ "$(echo "$reply" | head -n 1)" = "?" ] && [ -n "$2" ]; then + echo "$2" + continue + fi + + # removes trailing EOF and empty lines + reply=$(printf '%s\n' "$reply" | + sed 's/\x04$//' | + sed '/^[[:space:]]*$/d') + + break + done +} + +# the actual inputs :) + +required "Package repository (owner/repo)" \ + "The remote repository this is stored on. +You shouldn't include the host, just owner/repo is enough." + +REPO="$reply" + +required "Version of the bundled package" \ + "The tag or commit hash of the bundled package." + +VERSION="$reply" + +optional "Package name for find_package" \ + "When searching for system packages, this argument will be passed to find_package. +For example, using \"Boost\" here will result in CPMUtil internally calling find_package(Boost). +If unset, defaults to the JSON key." + +PACKAGE="$reply" + +optional "Minimum required version" \ + "The minimum required version for this package if it's pulled in by the system." + +MIN_VERSION="$reply" + +optional "Additional find_package arguments, space-separated" \ + "Extra arguments passed to find_package(), (e.g. CONFIG)" + +FIND_ARGS="$reply" + +optional "Git host (default: github.com)" \ + "The hostname of the Git server, if not GitHub (e.g. codeberg.org, git.crueter.xyz)" + +GIT_HOST="$reply" + +optional "Is this a CI package? [y/N]" \ + "Yes if the package is a prebuilt binary distribution (e.g. crueter-ci), +no if the package is built from source if it's bundled." + +case "$reply" in +[Yy]*) CI=true ;; +*) CI=false ;; +esac + +if [ "$CI" = "false" ]; then + optional "Name of the release artifact to download, if applicable. +-- %VERSION% is replaced by the version ($VERSION)" \ + "Download the specified artifact from the release with the previously specified tag." + + ARTIFACT="$reply" + + multi "Fixed options, one per line (e.g. OPUS_BUILD_TESTING OFF)" \ + "Fixed options passed to the project's CMakeLists.txt. Variadic options +should be set in CMake with AddJsonPackage's OPTIONS parameter." + + OPTIONS="$reply" +else + required "Name of the CI artifact" \ + "CI artifacts are stored as --.tar.zst. This option controls the name." + + ARTIFACT="$reply" + + multi "Platforms without a package (one per line)" \ + "Valid platforms: +windows-amd64 windows-arm64 +mingw-amd64 mingw-arm64 +android-aarch64 android-x86_64 +linux-amd64 linux-aarch64 +macos-universal ios-aarch64" + + DISABLED_PLATFORMS="$reply" +fi + +# invoke add.cmake for validation +set -- "$@" \ + -DKEY="$PKG" \ + -DREPO="$REPO" \ + -DVERSION="$VERSION" \ + -DCI="$CI" + +[ -z "$PACKAGE" ] || set -- "$@" -DPACKAGE="$PACKAGE" +[ -z "$GIT_HOST" ] || set -- "$@" -DGIT_HOST="$GIT_HOST" +[ -z "$MIN_VERSION" ] || set -- "$@" -DMIN_VERSION="$MIN_VERSION" +[ -z "$FIND_ARGS" ] || set -- "$@" -DFIND_ARGS="$FIND_ARGS" +[ -z "$OPTIONS" ] || set -- "$@" -DOPTIONS="$(echo "$OPTIONS" | tr '\n' ';')" +[ -z "$ARTIFACT" ] || set -- "$@" -DARTIFACT="$ARTIFACT" +[ -z "$DISABLED_PLATFORMS" ] || set -- "$@" -DDISABLED_PLATFORMS="$(echo "$DISABLED_PLATFORMS" | tr '\n' ';')" + +cmake "$@" -P "$CMAKE"/add.cmake -"$SCRIPTS"/util/interactive.sh +echo "Added package $PKG to cpmfile.json. Include it in your project with AddJsonPackage($PKG)" diff --git a/tools/cpm/package/dir.sh b/tools/cpm/package/dir.sh deleted file mode 100755 index 82a220509d..0000000000 --- a/tools/cpm/package/dir.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 -. "$SCRIPTS"/../common.sh - -usage() { - cat <&2 - RETURN=1 usage -} - -while :; do - case "$1" in - -h | --help) usage ;; - "$0") break ;; - "") break ;; - *) - if [ -n "$PACKAGE" ]; then - die "You may only specify one package." - else - PACKAGE="$1" - fi - ;; - esac - - shift -done - -[ -n "$PACKAGE" ] || usage - -unset JSON -export PACKAGE - -# shellcheck disable=SC1091 -. "$SCRIPTS"/vars.sh - -if [ "$CI" = true ]; then - die "CI packages do not support in-tree patching" -fi - -patch_dir="$PWD/.patch/$PACKAGE" -TMP=$(make_temp_dir) - -tmp_package="${TMP}/${LOWER_PACKAGE}/${KEY}" - -# First fetch the package... - -# shellcheck disable=SC1091 -. "$SCRIPTS"/util/fetch.sh - -local_dir="$CPM_SOURCE_CACHE/$LOWER_PACKAGE/$KEY" - -if [ ! -d "$local_dir" ]; then - echo "-- $PACKAGE_NAME is not fetched locally" >&2 +printf -- "-- Enter a patch description: " +if ! read -r description; then + echo "" >&2 + echo "-- Patch cancelled" >&2 + rm -rf "$TMP" exit 1 fi -src_dir="$tmp_package/$LOWER_PACKAGE/$KEY" - -CPM_SOURCE_CACHE="$tmp_package" fetch_package - -mkdir -p "$patch_dir" - -existing=$(find "$patch_dir" -maxdepth 1 -type f -name '[0-9][0-9][0-9][0-9]-*.patch' 2>/dev/null | sort | tail -1) -if [ -n "$existing" ]; then - last_num=$(basename "$existing" | cut -c1-4) - last_num=$(echo "$last_num" | sed 's/^0*//') - last_num="${last_num:-0}" - next_num=$(printf "%04d" $((last_num + 1))) -else - next_num="0001" -fi - -echo "-- Creating patch for $PACKAGE..." - -orig_dir="$PWD" -if cmd_exists git; then - cd "$src_dir" - - git init >/dev/null 2>&1 - git add -A >/dev/null 2>&1 - git commit -m "base" >/dev/null 2>&1 - - git --work-tree="$local_dir" add -A #>/dev/null 2>&1 - - if git diff --cached --quiet; then - echo "-- No differences found between local and source" - cd "$orig_dir" - rm -rf "$TMP" - exit 0 - fi - - if ! git commit -v; then - echo "-- Patch cancelled" >&2 - cd "$orig_dir" - rm -rf "$TMP" - exit 1 - fi - - description=$(git log -1 --format="%s") - - git format-patch -1 HEAD --stdout >"$patch_dir/tmp.patch" - rm -rf .git - - cd "$orig_dir" - unset orig_dir -else - if diff -ruN "$src_dir" "$local_dir" >/dev/null 2>&1; then - echo "-- No differences found between local and source" - rm -rf "$TMP" - exit 0 - fi - - printf -- "-- Enter a patch description: " - if ! read -r description; then - echo "" >&2 - echo "-- Patch cancelled" >&2 - rm -rf "$TMP" - exit 1 - fi - - mkdir -p "$TMP/patchdir/a" "$TMP/patchdir/b" - cp -r "$src_dir/." "$TMP/patchdir/a/" - cp -r "$local_dir/." "$TMP/patchdir/b/" - cd "$TMP/patchdir" - { - echo "Subject: [PATCH] $description" | fold -s -w 80 - echo "---" - diff -ruN a b || true - } >"$patch_dir/tmp.patch" - - cd "$orig_dir" - unset orig_dir -fi - -rm -rf "$TMP" - -if [ ! -s "$patch_dir/tmp.patch" ]; then - rm -f "$patch_dir/tmp.patch" - echo "-- No differences found between local and source for $PACKAGE" - exit 0 -fi - -name_part=$(printf "%s" "$description" | sed 's/^[0-9]\{4\}-//' | sed 's/ /-/g; s/[^a-zA-Z0-9-]//g; s/--*/-/g; s/^-//; s/-$//') -[ -n "$name_part" ] || name_part="patch" - -# max is 60 chars, minus 5 for number, minus 6 for patch suffix -max_name_len=49 -name_part=$(printf "%s" "$name_part" | cut -c1-"$max_name_len" | sed 's/-$//') - -patch_name="${next_num}-${name_part}.patch" - -mv "$patch_dir/tmp.patch" "$patch_dir/$patch_name" - -NEW_JSON=$(echo "$JSON" | jq ".patches += [\"$patch_name\"]") -"$SCRIPTS"/util/replace.sh "$PACKAGE" "$NEW_JSON" - -echo "-- Patch created at $patch_dir/$patch_name" +cmake -DDESCRIPTION="$description" -P "$CMAKE"/patch.cmake "$@" diff --git a/tools/cpm/package/reset.sh b/tools/cpm/package/reset.sh deleted file mode 100755 index 6e98c00d48..0000000000 --- a/tools/cpm/package/reset.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 -. "$SCRIPTS"/../common.sh - -usage() { - cat <cpmfile.json.new - mv cpmfile.json.new cpmfile.json - echo "-- Removed $pkg" -done diff --git a/tools/cpm/package/update.sh b/tools/cpm/package/update.sh deleted file mode 100755 index 96e143f662..0000000000 --- a/tools/cpm/package/update.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -filter_out() { - TAGS=$(echo "$TAGS" | jq "[.[] | select(.name | test(\"$1\"; \"i\") | not)]") -} - -filter_in() { - TAGS=$(echo "$TAGS" | jq "[.[] | select(.name | test(\"$1\"; \"i\"))]") -} - -usage() { - cat </dev/null 2>&1; then - TAGS=$(gh api --method GET "$endpoint") - elif [ "$GIT_HOST" = github.com ]; then - TAGS=$(curl -sfL "https://api.github.com$endpoint") - else - TAGS=$(curl -sfL "https://$GIT_HOST/api/v1$endpoint") - fi - - # filter out some commonly known annoyances - # TODO add more - - if [ "$PACKAGE" = "vulkan-validation-layers" ]; then - filter_in vulkan-sdk - else - filter_out vulkan-sdk - fi - - filter_out yotta # mbedtls - - filter_out vksc - - # ignore betas/alphas (remove if needed) - filter_out alpha - filter_out beta - filter_out rc - - # Add package-specific overrides here, e.g. here for fmt: - [ "$PACKAGE" != fmt ] || filter_out v0.11 - - # Or for OpenSSL: - if [ "$PACKAGE" = openssl ]; then - filter_out rsaref - filter_in "openssl-" - fi - - LATEST=$(echo "$TAGS" | jq -r '.[0].name') - - if [ "$LATEST" = "null" ] || - { [ "$LATEST" = "$TAG" ] && [ "$FORCE" != "true" ]; }; then - echo "-- * Up-to-date" - continue - fi - - # TODO: This is identical to version.sh - - if [ "$HAS_REPLACE" = "true" ]; then - # this just extracts the tag prefix - VERSION_PREFIX=$(echo "$ORIGINAL_TAG" | cut -d"%" -f1) - - # then we strip out the prefix from the new tag, and make that our new version - if [ -z "$VERSION_PREFIX" ]; then - NEW_VERSION="$LATEST" - else - NEW_VERSION=$(echo "$LATEST" | sed "s/$VERSION_PREFIX//g") - fi - else - NEW_VERSION="$LATEST" - fi - - _commit="$_commit -* $PACKAGE: $VERSION -> $NEW_VERSION" - - echo "-- * Version $LATEST available, current is $TAG" - - if [ "$UPDATE" = "true" ]; then - if [ "$HAS_REPLACE" = "true" ]; then - JSON=$(echo "$JSON" | jq ".version = \"$NEW_VERSION\"") - else - JSON=$(echo "$JSON" | jq ".tag = \"$NEW_VERSION\"") - fi - - echo "-- * -- Updating hash" - - # shellcheck disable=SC1091 - . "$SCRIPTS"/vars.sh - HASH=$("$SCRIPTS"/util/url-hash.sh "$DOWNLOAD") - JSON=$(echo "$JSON" | jq ".hash = \"$HASH\"") - - "$SCRIPTS"/util/replace.sh "$PACKAGE" "$JSON" - fi -done - -if [ "$UPDATE" = "true" ] && [ "$COMMIT" = "true" ] && [ -n "$_commit" ]; then - git add "cpmfile.json" - git commit -m "Update dependencies -$_commit" -fi diff --git a/tools/cpm/package/util/fetch.sh b/tools/cpm/package/util/fetch.sh deleted file mode 100755 index 7db568f3e6..0000000000 --- a/tools/cpm/package/util/fetch.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 -. "$SCRIPTS"/../common.sh - -download_package() { - mkdir -p "$CPM_SOURCE_CACHE" - - tmp=$(make_temp_dir) - - FILENAME=$(basename "$DOWNLOAD") - - OUTFILE="$tmp/$FILENAME" - - OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}" - if [ -d "$OUTDIR" ]; then return; fi - - curl "$DOWNLOAD" -sS -L -o "$OUTFILE" - - ACTUAL_HASH=$(sha512sum "$OUTFILE" | cut -d" " -f1) - if [ "$ACTUAL_HASH" != "$HASH" ]; then - echo "!! $FILENAME did not match expected hash; expected $HASH but got $ACTUAL_HASH" >&2 - exit 1 - fi - - TMPDIR="$tmp/extracted" - mkdir -p "$OUTDIR" - mkdir -p "$TMPDIR" - - PREVDIR="$PWD" - mkdir -p "$TMPDIR" - cd "$TMPDIR" - - case "$FILENAME" in - *.7z) - must_install 7z - 7z x "$OUTFILE" >/dev/null - ;; - *.tar*) - # TODO: Extensions - must_install tar - tar xf "$OUTFILE" >/dev/null - ;; - *.zip) - must_install unzip - unzip "$OUTFILE" >/dev/null - ;; - esac - - # basically if only one real item exists at the top we just move everything from there - # since github and some vendors hate me - DIRS=$(find . -maxdepth 1 -type d -o -type f) - - # thanks gnu - if [ "$(echo "$DIRS" | wc -l)" -eq 2 ]; then - SUBDIR=$(find . -maxdepth 1 -type d -not -name ".") - mv "$SUBDIR"/* "$OUTDIR" - mv "$SUBDIR"/.* "$OUTDIR" 2>/dev/null || true - rmdir "$SUBDIR" - else - mv ./* "$OUTDIR" - mv ./.* "$OUTDIR" 2>/dev/null || true - fi - - cd "$OUTDIR" - - # TODO: Common custom patch/source cache dirs - if echo "$JSON" | grep -e "patches" >/dev/null; then - PATCHES=$(echo "$JSON" | jq -r '.patches | join(" ")') - for patch in $PATCHES; do - abs_patch="$CPMUTIL_PATCH_DIR/$PACKAGE/$patch" - if [ ! -f "$abs_patch" ]; then - echo "-- * Attempted to apply nonexistent patch $patch!" - continue - fi - - echo "-- * Applying patch $patch" - patch --binary -p1 <"$abs_patch" - done - fi - - cd "$PREVDIR" - - rm -rf "$tmp" -} - -# TODO: individual platform fetch? -ci_package() { - [ "$REPO" != null ] || { echo "-- ! No repo defined" && return; } - mkdir -p "$CPM_SOURCE_CACHE" - - echo "-- CI package $PACKAGE_NAME" - - for platform in \ - windows-amd64 windows-arm64 \ - mingw-amd64 mingw-arm64 \ - android-aarch64 android-x86_64 \ - linux-amd64 linux-aarch64 \ - macos-universal ios-aarch64; do - echo "-- * platform $platform" - - case $DISABLED in - *"$platform"*) - echo "-- * -- disabled" - continue - ;; - esac - - FILENAME="${NAME}-${platform}-${VERSION}.${EXT}" - DOWNLOAD="https://$GIT_HOST/${REPO}/releases/download/v${VERSION}/${FILENAME}" - KEY="$platform-$VERSION" - - OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}" - [ -d "$OUTDIR" ] && continue - - HASH_URL="${DOWNLOAD}.sha512sum" - - HASH=$(curl "$HASH_URL" -sS -q -L -o -) - - download_package - done -} - -fetch_package() { - if [ "$CI" = "true" ]; then - ci_package - else - echo "-- Downloading regular package $PACKAGE, with key $KEY, from $DOWNLOAD" - download_package - fi -} diff --git a/tools/cpm/package/util/fix-hash.sh b/tools/cpm/package/util/fix-hash.sh deleted file mode 100755 index 476b15af89..0000000000 --- a/tools/cpm/package/util/fix-hash.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 - -: "${PACKAGE:=$1}" - -# re-read json files -# shellcheck disable=SC2016 -. "$SCRIPTS"/vars.sh - -[ "$CI" = null ] || exit 0 - -ACTUAL=$("$SCRIPTS"/util/url-hash.sh "$DOWNLOAD") - -if [ "$ACTUAL" != "$HASH" ] && [ "$QUIET" != true ]; then - echo "-- * Expected $HASH" - echo "-- * Got $ACTUAL" - [ "$UPDATE" != "true" ] && exit 1 -fi - -if [ "$UPDATE" = "true" ] && [ "$ACTUAL" != "$HASH" ]; then - NEW_JSON=$(echo "$JSON" | jq ".hash = \"$ACTUAL\"") - - "$SCRIPTS"/util/replace.sh "$PACKAGE" "$NEW_JSON" -fi diff --git a/tools/cpm/package/util/interactive.sh b/tools/cpm/package/util/interactive.sh deleted file mode 100755 index 1f5a16aa07..0000000000 --- a/tools/cpm/package/util/interactive.sh +++ /dev/null @@ -1,228 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# This reads a single-line input from the user and also gives them -# help if needed. -# $1: The prompt itself, without any trailing spaces or whatever -# $2: The help text that gets shown when the user types a question mark -# $3: This is set to "required" if it's necessary, -# otherwise it can continue without input. -# Stores its output in the "reply" variable -read_single() { - while :; do - printf -- "-- %s" "$1" - [ -z "$2" ] || printf " (? for help, %s)" "$3" - printf ": " - if ! IFS= read -r reply; then - echo - [ "$3" = "required" ] && continue || reply="" - fi - case "$reply" in - "?") echo "$2" ;; - "") [ "$3" = "required" ] && continue || return 0 ;; - *) return 0 ;; - esac - done -} - -# read_single, but optional -optional() { - read_single "$1" "$2" "optional" -} - -# a -required() { - read_single "$1" "$2" "required" -} - -# Basically the same as the single line function except multiline, -# also it's never "required" so we don't need that handling. -multi() { - echo "-- $1" - if [ -n "$2" ]; then - echo "-- (? on first line for help, Ctrl-D to finish)" - else - echo "-- (Ctrl-D to finish)" - fi - while :; do - reply=$(cat) - if [ "$(echo "$reply" | head -n 1)" = "?" ] && [ -n "$2" ]; then - echo "$2" - continue - fi - - # removes trailing EOF and empty lines - reply=$(printf '%s\n' "$reply" | - sed 's/\x04$//' | - sed '/^[[:space:]]*$/d') - - break - done -} - -# the actual inputs :) - -required "Package repository (owner/repo)" \ - "The remote repository this is stored on. -You shouldn't include the host, just owner/repo is enough." - -REPO="$reply" - -optional "Package name for find_package" \ - "When searching for system packages, this argument will be passed to find_package. -For example, using \"Boost\" here will result in CPMUtil internally calling find_package(Boost)." - -PACKAGE="$reply" - -optional "Minimum required version" \ - "The minimum required version for this package if it's pulled in by the system." - -MIN_VERSION="$reply" - -optional "Additional find_package arguments, space-separated" \ - "Extra arguments passed to find_package(), (e.g. CONFIG)" - -FIND_ARGS="$reply" - -optional "Git host (default: github.com)" \ - "The hostname of the Git server, if not GitHub (e.g. codeberg.org, git.crueter.xyz)" - -GIT_HOST="$reply" - -required "Numeric version of the bundled package" \ - "The semantic version of the bundled package. This is only used for package identification, -and if you use tag/artifact fetching. Do not input the entire tag here; for example, if you're using -tag v1.3.0, then set this to 1.3.0 and set the tag to v%VERSION%." - -VERSION="$reply" - -optional "Is this a CI package? [y/N]" \ - "Yes if the package is a prebuilt binary distribution (e.g. crueter-ci), -no if the package is built from source if it's bundled." - -case "$reply" in -[Yy]*) CI=true ;; -*) CI=false ;; -esac - -if [ "$CI" = "false" ]; then - while :; do - required "Use tag or commit sha versioning? [tag/sha]" \ - "Tag versioning is compatible with auto-updating. -Use sha versioning for projects with improper tagging practices" - - if [ "$reply" = "tag" ]; then - TAG=1 - break - elif [ "$reply" = "sha" ]; then - SHA=1 - break - else - echo "-- Invalid choice $reply" - fi - done - - if [ "$TAG" = "1" ]; then - optional "Name of the upstream tag. %VERSION% is replaced by the numeric version ($VERSION) (default: %VERSION%)" \ - "Most commonly this will be something like v%VERSION% or release-%VERSION%, or just %VERSION%." - - TAGNAME="$reply" - [ -n "$TAGNAME" ] || TAGNAME="%VERSION%" - - optional "Name of the release artifact to download, if applicable. --- %VERSION% is replaced by the numeric version ($VERSION) and %TAG% is replaced by the tag name" \ - "Download the specified artifact from the release with the previously specified tag. -If unspecified, the source code at the specified tag will be used instead." - - ARTIFACT="$reply" - else - required "Commit sha" \ - "The short Git commit sha to use. You're recommended to keep this short, e.g. 10 characters." - - SHA="$reply" - fi - - multi "Fixed options, one per line (e.g. OPUS_BUILD_TESTING OFF)" \ - "Fixed options passed to the project's CMakeLists.txt. Variadic options -should be set in CMake with AddJsonPackage's OPTIONS parameter." - - OPTIONS="$reply" -else - required "Name of the CI artifact" \ - "CI artifacts are stored as --.tar.zst. This option controls the name." - - ARTIFACT="$reply" - - multi "Platforms without a package (one per line)" \ - "Valid platforms: -windows-amd64 windows-arm64 -mingw-amd64 mingw-arm64 -android-aarch64 android-x86_64 -linux-amd64 linux-aarch64 -macos-universal ios-aarch64" - - DISABLED_PLATFORMS="$reply" -fi - -# now time to construct the actual json -jq_input='{repo: "'"$REPO"'"}' - -# common trivial fields -[ -z "$PACKAGE" ] || jq_input="$jq_input + {package: \"$PACKAGE\"}" -[ -z "$MIN_VERSION" ] || jq_input="$jq_input + {min_version: \"$MIN_VERSION\"}" -[ -z "$FIND_ARGS" ] || jq_input="$jq_input + {find_args: \"$FIND_ARGS\"}" -jq_input="$jq_input + {version: \"$VERSION\"}" - -if [ -n "$GIT_HOST" ] && [ "$GIT_HOST" != "github.com" ]; then - jq_input="$jq_input + {git_host: \"$GIT_HOST\"}" -fi - -if [ "$CI" = "true" ]; then - jq_input="$jq_input + { - ci: true, - artifact: \"$ARTIFACT\" - }" - - # disabled platforms - if [ -n "$DISABLED_PLATFORMS" ] && [ -n "$(printf '%s' "$DISABLED_PLATFORMS" | tr -d ' \t\n\r')" ]; then - disabled_json=$(printf '%s\n' "$DISABLED_PLATFORMS" | jq -R . | jq -s .) - jq_input="$jq_input + {disabled_platforms: $disabled_json}" - fi -else - [ -z "$MIN_VERSION" ] || jq_input="$jq_input + {version: \"$MIN_VERSION\"}" - jq_input="$jq_input + {hash: \"\"}" - - # options - if [ -n "$OPTIONS" ] && [ -n "$(printf '%s' "$OPTIONS" | tr -d ' \t\n\r')" ]; then - options_json=$(printf '%s\n' "$OPTIONS" | jq -R . | jq -s .) - jq_input="$jq_input + {options: $options_json}" - fi - - # versioning stuff - if [ "$TAG" = 1 ]; then - jq_input="$jq_input + {tag: \"$TAGNAME\"}" - [ -z "$ARTIFACT" ] || jq_input="$jq_input + {artifact: \"$ARTIFACT\"}" - else - jq_input="$jq_input + {sha: \"$SHA\"}" - fi -fi - -JSON=$(jq -n "$jq_input") - -# shellcheck disable=SC1091 -. "$SCRIPTS"/vars.sh - -if [ "$CI" != true ]; then - HASH=$("$SCRIPTS"/util/url-hash.sh "$DOWNLOAD") - JSON=$(echo "$JSON" | jq ".hash = \"$HASH\"") -fi - -jq --arg key "$PKG" --argjson new "$JSON" \ - '.[$key] = $new' "cpmfile.json" --indent 4 >"cpmfile.json.tmp" && - mv "cpmfile.json.tmp" cpmfile.json - -"$SCRIPTS"/format.sh - -echo "Added package $PKG to cpmfile.json. Include it in your project with AddJsonPackage($PKG)" diff --git a/tools/cpm/package/util/replace.sh b/tools/cpm/package/util/replace.sh deleted file mode 100755 index 7141747a12..0000000000 --- a/tools/cpm/package/util/replace.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# Replace a specified package with a modified json. - -jq --indent 4 --argjson repl "$2" ".\"$1\" *= \$repl" cpmfile.json >cpmfile.json.new -mv cpmfile.json.new cpmfile.json - -echo "-- * -- Updated cpmfile.json" diff --git a/tools/cpm/package/util/url-hash.sh b/tools/cpm/package/util/url-hash.sh deleted file mode 100755 index eae784b53d..0000000000 --- a/tools/cpm/package/util/url-hash.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -SUM=$(curl -Ls "$1" -o - | sha512sum) -echo "$SUM" | cut -d " " -f1 diff --git a/tools/cpm/package/util/url.sh b/tools/cpm/package/util/url.sh deleted file mode 100755 index 22a69bf898..0000000000 --- a/tools/cpm/package/util/url.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# Get the download URL for a package - -if [ "$URL" != "null" ]; then - DOWNLOAD="$URL" -elif [ "$REPO" != "null" ]; then - GIT_URL="https://$GIT_HOST/$REPO" - - if [ "$SHA" != "null" ]; then - DOWNLOAD="${GIT_URL}/archive/${SHA}.tar.gz" - elif [ "$TAG" != "null" ]; then - if [ "$ARTIFACT" != "null" ]; then - DOWNLOAD="${GIT_URL}/releases/download/${TAG}/${ARTIFACT}" - else - DOWNLOAD="${GIT_URL}/archive/refs/tags/${TAG}.tar.gz" - fi - fi -else - echo "!! No repo or URL defined for $PACKAGE_NAME" - exit 1 -fi - -export DOWNLOAD diff --git a/tools/cpm/package/vars.sh b/tools/cpm/package/vars.sh deleted file mode 100755 index dd497407c8..0000000000 --- a/tools/cpm/package/vars.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 - -value() { - echo "$JSON" | jq -r ".$1" -} - -if [ -z "$JSON" ]; then - [ -n "$PACKAGE" ] || { echo "Package was not specified" && exit 0; } - - # shellcheck disable=SC2153 - JSON=$(jq -r ".\"$PACKAGE\" | select( . != null )" cpmfile.json) - - if [ -z "$JSON" ]; then - echo "!! No cpmfile definition for $PACKAGE" >&2 - exit 1 - fi -fi - -# unset stuff -export PACKAGE_NAME="null" -export REPO="null" -export CI="null" -export GIT_HOST="null" -export EXT="null" -export NAME="null" -export DISABLED="null" -export TAG="null" -export ARTIFACT="null" -export SHA="null" -export VERSION="null" -export MIN_VERSION="null" -export DOWNLOAD="null" -export URL="null" -export KEY="null" -export HASH="null" -export ORIGINAL_TAG="null" -export HAS_REPLACE="null" -export VERSION_REPLACE="null" - -######## -# Meta # -######## - -REPO=$(value "repo") -CI=$(value "ci") - -PACKAGE_NAME=$(value "package") -[ "$PACKAGE_NAME" != null ] || PACKAGE_NAME="$PACKAGE" - -GIT_HOST=$(value "git_host") -[ "$GIT_HOST" != null ] || GIT_HOST=github.com - -# used for cache key -LOWER_PACKAGE=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]') - -export PACKAGE_NAME -export LOWER_PACKAGE -export REPO -export CI -export GIT_HOST - -###################### -# CI Package Parsing # -###################### - -MIN_VERSION=$(value "min_version") -VERSION=$(value "version") - -export VERSION -export MIN_VERSION - -if [ "$CI" = "true" ]; then - EXT=$(value "extension") - [ "$EXT" != null ] || EXT="tar.zst" - - NAME=$(value "name") - DISABLED=$(echo "$JSON" | jq -j '.disabled_platforms') - - [ "$NAME" != null ] || NAME="$PACKAGE_NAME" - - export EXT - export NAME - export DISABLED - - return 0 -fi - -############## -# Versioning # -############## - -TAG=$(value "tag") -ARTIFACT=$(value "artifact") -SHA=$(value "sha") - -if echo "$TAG" | grep -e "%VERSION%" >/dev/null; then - HAS_REPLACE=true -else - HAS_REPLACE=false -fi - -ORIGINAL_TAG="$TAG" - -TAG=$(echo "$TAG" | sed "s/%VERSION%/$VERSION/g") -ARTIFACT=$(echo "$ARTIFACT" | sed "s/%VERSION%/$VERSION/g") -ARTIFACT=$(echo "$ARTIFACT" | sed "s/%TAG%/$TAG/g") - -export TAG -export ARTIFACT -export SHA -export VERSION -export ORIGINAL_TAG -export HAS_REPLACE - -############### -# URL Parsing # -############### - -URL=$(value "url") - -. "$SCRIPTS"/util/url.sh - -export DOWNLOAD - -############### -# Key Parsing # -############### - -if [ "$SHA" != null ]; then - KEY=$(echo "$SHA" | cut -c1-4) -elif [ "$VERSION" != null ]; then - KEY="$VERSION" -elif [ "$TAG" != null ]; then - KEY="$TAG" -else - echo "!! No valid key could be determined for $PACKAGE_NAME. Must define one of: sha, tag, version" - exit 1 -fi - -export KEY - -################ -# Hash Parsing # -################ - -HASH=$(value "hash") - -if [ "$HASH" = null ]; then - echo "!! No hash defined for $PACKAGE_NAME" >&2 -fi - -export HASH -export JSON diff --git a/tools/cpm/package/version.sh b/tools/cpm/package/version.sh deleted file mode 100755 index cfeaa27f1e..0000000000 --- a/tools/cpm/package/version.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 - -usage() { - cat </dev/null 2>&1 diff --git a/tools/cpmutil.sh b/tools/cpmutil.sh index 6c18e6999b..b1edbbde4b 100755 --- a/tools/cpmutil.sh +++ b/tools/cpmutil.sh @@ -7,8 +7,7 @@ ROOTDIR=$(CDPATH='' cd -- "$(dirname -- "$0")/cpm" && pwd) SCRIPTS="$ROOTDIR" - -. "$SCRIPTS"/common.sh +CMAKE="$ROOTDIR/cmake" RETURN=0 @@ -26,33 +25,55 @@ General command-line utility for CPMUtil operations. Commands: package Run operations on a package or packages format Format cpmfile + ls List all packages in the cpmfile update Update CPMUtil and its tooling migrate Convert submodules to a basic cpmfile Package commands: - hash Verify the hash of a package, and update it if needed - update Check for updates for a package - fetch Fetch a package and place it in the cache - add Add a new package - rm Remove a package - version Change the version of a package - which Check if a package is defined - download Get the download URL for a package - dir Get the local directory for a package - reset Reset a fetched package to its original state - patch Create an in-tree patch based on local modifications + hash Verify the hash of a package, and update it if needed + update Check for updates for a package + fetch Fetch a package and place it in the cache + add Add a new package + rm Remove a package + version Change the version of a package + which Check if a package is defined + url Get the download URL for a package + dir Get the local directory for a package + reset Reset a fetched package to its original state + patch Create an in-tree patch based on local modifications EOF exit $RETURN } -export ROOTDIR +export ROOTDIR CMAKE SCRIPTS while :; do case "$1" in - format | update | migrate) - "$SCRIPTS/$1".sh + format | update) + cmake -P "$CMAKE/$1".cmake + break + ;; + ls) + shift + print_usage=false + filtered="" + for arg in "$@"; do + case "$arg" in + -h | --help) print_usage=true ;; + -n) ;; + *) filtered="$filtered $arg" ;; + esac + done + cmake_defines="" + $print_usage && cmake_defines="-DPRINT_USAGE=TRUE" + # shellcheck disable=SC2086 + cmake $cmake_defines -P "$CMAKE/ls.cmake" -- $filtered + break + ;; + migrate) + "$SCRIPTS"/migrate.sh break ;; package)