Browse Source

[cmake, docs, tools] update CPMUtil

Rewrote the entire tooling scheme. That's about it, just make sure
tooling works as expected everywhere.

Signed-off-by: crueter <crueter@eden-emu.dev>
update-cpmutil
crueter 3 days ago
parent
commit
d754fca70f
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 162
      CMakeModules/CPMUtil.cmake
  2. 17
      docs/CPMUtil/AddCIPackage
  3. 19
      docs/CPMUtil/README.md
  4. 7
      tools/cpm-fetch-all.sh
  5. 80
      tools/cpm/README.md
  6. 10
      tools/cpm/check-hash-all.sh
  7. 79
      tools/cpm/check-hash.sh
  8. 10
      tools/cpm/check-updates-all.sh
  9. 14
      tools/cpm/common.sh
  10. 110
      tools/cpm/download.sh
  11. 10
      tools/cpm/fetch-all.sh
  12. 60
      tools/cpm/fetch.sh
  13. 11
      tools/cpm/format.sh
  14. 99
      tools/cpm/hash.sh
  15. 47
      tools/cpm/migrate.sh
  16. 276
      tools/cpm/package.sh
  17. 79
      tools/cpm/package/add.sh
  18. 46
      tools/cpm/package/download.sh
  19. 156
      tools/cpm/package/fetch.sh
  20. 66
      tools/cpm/package/hash.sh
  21. 30
      tools/cpm/package/rm.sh
  22. 96
      tools/cpm/package/update.sh
  23. 30
      tools/cpm/package/util/fix-hash.sh
  24. 217
      tools/cpm/package/util/interactive.sh
  25. 13
      tools/cpm/package/util/replace.sh
  26. 7
      tools/cpm/package/util/url-hash.sh
  27. 170
      tools/cpm/package/vars.sh
  28. 21
      tools/cpm/package/vars/key.sh
  29. 32
      tools/cpm/package/vars/url.sh
  30. 60
      tools/cpm/package/version.sh
  31. 12
      tools/cpm/package/which.sh
  32. 20
      tools/cpm/replace.sh
  33. 8
      tools/cpm/update.sh
  34. 7
      tools/cpm/url-hash.sh
  35. 15
      tools/cpm/which.sh
  36. 78
      tools/cpmutil.sh

162
CMakeModules/CPMUtil.cmake

@ -1,7 +1,9 @@
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
if (MSVC OR ANDROID)
set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/.cache/cpm" CACHE STRING "" FORCE)
if(MSVC OR ANDROID)
set(BUNDLED_DEFAULT ON)
else()
set(BUNDLED_DEFAULT OFF)
@ -19,7 +21,7 @@ include(CPM)
# cpmfile parsing
set(CPMUTIL_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json")
if (EXISTS ${CPMUTIL_JSON_FILE})
if(EXISTS ${CPMUTIL_JSON_FILE})
file(READ ${CPMUTIL_JSON_FILE} CPMFILE_CONTENT)
else()
message(WARNING "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE} does not exist, AddJsonPackage will be a no-op")
@ -45,14 +47,14 @@ endfunction()
function(get_json_element object out member default)
string(JSON out_type ERROR_VARIABLE err TYPE "${object}" ${member})
if (err)
if(err)
set("${out}" "${default}" PARENT_SCOPE)
return()
endif()
string(JSON outvar GET "${object}" ${member})
if (out_type STREQUAL "ARRAY")
if(out_type STREQUAL "ARRAY")
string(JSON _len LENGTH "${object}" ${member})
# array_to_list("${outvar}" ${_len} outvar)
set("${out}_LENGTH" "${_len}" PARENT_SCOPE)
@ -74,7 +76,7 @@ function(AddJsonPackage)
set(multiValueArgs OPTIONS)
cmake_parse_arguments(JSON "" "${oneValueArgs}" "${multiValueArgs}"
"${ARGN}")
"${ARGN}")
list(LENGTH ARGN argnLength)
@ -83,18 +85,18 @@ function(AddJsonPackage)
set(JSON_NAME "${ARGV0}")
endif()
if (NOT DEFINED CPMFILE_CONTENT)
if(NOT DEFINED CPMFILE_CONTENT)
cpm_utils_message(WARNING ${name} "No cpmfile, AddJsonPackage is a no-op")
return()
endif()
if (NOT DEFINED JSON_NAME)
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)
if(err)
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile")
endif()
@ -103,13 +105,13 @@ function(AddJsonPackage)
get_json_element("${object}" ci ci OFF)
get_json_element("${object}" version version "")
if (ci)
if(ci)
get_json_element("${object}" name name "${JSON_NAME}")
get_json_element("${object}" extension extension "tar.zst")
get_json_element("${object}" min_version min_version "")
get_json_element("${object}" raw_disabled disabled_platforms "")
if (raw_disabled)
if(raw_disabled)
array_to_list("${raw_disabled}" ${raw_disabled_LENGTH} disabled_platforms)
else()
set(disabled_platforms "")
@ -154,31 +156,31 @@ function(AddJsonPackage)
# first: tag gets %VERSION% replaced if applicable, with either git_version (preferred) or version
# second: artifact gets %VERSION% and %TAG% replaced accordingly (same rules for VERSION)
if (git_version)
if(git_version)
set(version_replace ${git_version})
else()
set(version_replace ${version})
endif()
# TODO(crueter): fmt module for cmake
if (tag)
if(tag)
string(REPLACE "%VERSION%" "${version_replace}" tag ${tag})
endif()
if (artifact)
if(artifact)
string(REPLACE "%VERSION%" "${version_replace}" artifact ${artifact})
string(REPLACE "%TAG%" "${tag}" artifact ${artifact})
endif()
# format patchdir
if (raw_patches)
if(raw_patches)
math(EXPR range "${raw_patches_LENGTH} - 1")
foreach(IDX RANGE ${range})
string(JSON _patch GET "${raw_patches}" "${IDX}")
set(full_patch "${CMAKE_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}")
if (NOT EXISTS ${full_patch})
if(NOT EXISTS ${full_patch})
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "specifies patch ${full_patch} which does not exist")
endif()
@ -190,7 +192,7 @@ function(AddJsonPackage)
# options
get_json_element("${object}" raw_options options "")
if (raw_options)
if(raw_options)
array_to_list("${raw_options}" ${raw_options_LENGTH} options)
endif()
@ -198,7 +200,7 @@ function(AddJsonPackage)
# end options
# system/bundled
if (bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
if(bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
set(bundled ${JSON_BUNDLED_PACKAGE})
endif()
@ -284,37 +286,37 @@ function(AddPackage)
set(multiValueArgs OPTIONS PATCHES)
cmake_parse_arguments(PKG_ARGS "" "${oneValueArgs}" "${multiValueArgs}"
"${ARGN}")
"${ARGN}")
if (NOT DEFINED PKG_ARGS_NAME)
if(NOT DEFINED PKG_ARGS_NAME)
cpm_utils_message(FATAL_ERROR "package" "No package name defined")
endif()
option(${PKG_ARGS_NAME}_FORCE_SYSTEM "Force the system package for ${PKG_ARGS_NAME}")
option(${PKG_ARGS_NAME}_FORCE_BUNDLED "Force the bundled package for ${PKG_ARGS_NAME}")
if (NOT DEFINED PKG_ARGS_GIT_HOST)
if(NOT DEFINED PKG_ARGS_GIT_HOST)
set(git_host github.com)
else()
set(git_host ${PKG_ARGS_GIT_HOST})
endif()
if (DEFINED PKG_ARGS_URL)
if(DEFINED PKG_ARGS_URL)
set(pkg_url ${PKG_ARGS_URL})
if (DEFINED PKG_ARGS_REPO)
if(DEFINED PKG_ARGS_REPO)
set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO})
else()
if (DEFINED PKG_ARGS_GIT_URL)
if(DEFINED PKG_ARGS_GIT_URL)
set(pkg_git_url ${PKG_ARGS_GIT_URL})
else()
set(pkg_git_url ${pkg_url})
endif()
endif()
elseif (DEFINED PKG_ARGS_REPO)
elseif(DEFINED PKG_ARGS_REPO)
set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO})
if (DEFINED PKG_ARGS_TAG)
if(DEFINED PKG_ARGS_TAG)
set(pkg_key ${PKG_ARGS_TAG})
if(DEFINED PKG_ARGS_ARTIFACT)
@ -324,14 +326,14 @@ function(AddPackage)
set(pkg_url
${pkg_git_url}/archive/refs/tags/${PKG_ARGS_TAG}.tar.gz)
endif()
elseif (DEFINED PKG_ARGS_SHA)
elseif(DEFINED PKG_ARGS_SHA)
set(pkg_url "${pkg_git_url}/archive/${PKG_ARGS_SHA}.tar.gz")
else()
if (DEFINED PKG_ARGS_BRANCH)
if(DEFINED PKG_ARGS_BRANCH)
set(PKG_BRANCH ${PKG_ARGS_BRANCH})
else()
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
"REPO defined but no TAG, SHA, BRANCH, or URL specified, defaulting to master")
"REPO defined but no TAG, SHA, BRANCH, or URL specified, defaulting to master")
set(PKG_BRANCH master)
endif()
@ -343,72 +345,72 @@ function(AddPackage)
cpm_utils_message(STATUS ${PKG_ARGS_NAME} "Download URL is ${pkg_url}")
if (NOT DEFINED PKG_ARGS_KEY)
if (DEFINED PKG_ARGS_SHA)
if(NOT DEFINED PKG_ARGS_KEY)
if(DEFINED PKG_ARGS_SHA)
string(SUBSTRING ${PKG_ARGS_SHA} 0 4 pkg_key)
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
"No custom key defined, using ${pkg_key} from sha")
"No custom key defined, using ${pkg_key} from sha")
elseif(DEFINED PKG_ARGS_GIT_VERSION)
set(pkg_key ${PKG_ARGS_GIT_VERSION})
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
"No custom key defined, using ${pkg_key}")
elseif (DEFINED PKG_ARGS_TAG)
"No custom key defined, using ${pkg_key}")
elseif(DEFINED PKG_ARGS_TAG)
set(pkg_key ${PKG_ARGS_TAG})
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
"No custom key defined, using ${pkg_key}")
elseif (DEFINED PKG_ARGS_VERSION)
"No custom key defined, using ${pkg_key}")
elseif(DEFINED PKG_ARGS_VERSION)
set(pkg_key ${PKG_ARGS_VERSION})
cpm_utils_message(DEBUG ${PKG_ARGS_NAME}
"No custom key defined, using ${pkg_key}")
"No custom key defined, using ${pkg_key}")
else()
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
"Could not determine cache key, using CPM defaults")
"Could not determine cache key, using CPM defaults")
endif()
else()
set(pkg_key ${PKG_ARGS_KEY})
endif()
if (DEFINED PKG_ARGS_HASH_ALGO)
if(DEFINED PKG_ARGS_HASH_ALGO)
set(hash_algo ${PKG_ARGS_HASH_ALGO})
else()
set(hash_algo SHA512)
endif()
if (DEFINED PKG_ARGS_HASH)
if(DEFINED PKG_ARGS_HASH)
set(pkg_hash "${hash_algo}=${PKG_ARGS_HASH}")
elseif (DEFINED PKG_ARGS_HASH_SUFFIX)
elseif(DEFINED PKG_ARGS_HASH_SUFFIX)
# funny sanity check
string(TOLOWER ${hash_algo} hash_algo_lower)
string(TOLOWER ${PKG_ARGS_HASH_SUFFIX} suffix_lower)
if (NOT ${suffix_lower} MATCHES ${hash_algo_lower})
if(NOT ${suffix_lower} MATCHES ${hash_algo_lower})
cpm_utils_message(WARNING
"Hash algorithm and hash suffix do not match, errors may occur")
"Hash algorithm and hash suffix do not match, errors may occur")
endif()
set(hash_url ${pkg_url}.${PKG_ARGS_HASH_SUFFIX})
elseif (DEFINED PKG_ARGS_HASH_URL)
elseif(DEFINED PKG_ARGS_HASH_URL)
set(hash_url ${PKG_ARGS_HASH_URL})
else()
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
"No hash or hash URL found")
"No hash or hash URL found")
endif()
if (DEFINED hash_url)
if(DEFINED hash_url)
set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${PKG_ARGS_NAME}.hash)
# TODO(crueter): This is kind of a bad solution
# because "technically" the hash is invalidated each week
# but it works for now kjsdnfkjdnfjksdn
string(TOLOWER ${PKG_ARGS_NAME} lowername)
if (NOT EXISTS ${outfile} AND NOT EXISTS ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key})
if(NOT EXISTS ${outfile} AND NOT EXISTS ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key})
file(DOWNLOAD ${hash_url} ${outfile})
endif()
if (EXISTS ${outfile})
if(EXISTS ${outfile})
file(READ ${outfile} pkg_hash_tmp)
endif()
if (DEFINED ${pkg_hash_tmp})
if(DEFINED ${pkg_hash_tmp})
set(pkg_hash "${hash_algo}=${pkg_hash_tmp}")
endif()
endif()
@ -426,19 +428,19 @@ function(AddPackage)
- CPMUTIL_FORCE_BUNDLED
- BUNDLED_PACKAGE
- default to allow local
]]#
if (PKG_ARGS_FORCE_BUNDLED_PACKAGE)
]] #
if(PKG_ARGS_FORCE_BUNDLED_PACKAGE)
set_precedence(OFF OFF)
elseif (${PKG_ARGS_NAME}_FORCE_SYSTEM)
elseif(${PKG_ARGS_NAME}_FORCE_SYSTEM)
set_precedence(ON ON)
elseif (${PKG_ARGS_NAME}_FORCE_BUNDLED)
elseif(${PKG_ARGS_NAME}_FORCE_BUNDLED)
set_precedence(OFF OFF)
elseif (CPMUTIL_FORCE_SYSTEM)
elseif(CPMUTIL_FORCE_SYSTEM)
set_precedence(ON ON)
elseif(CPMUTIL_FORCE_BUNDLED)
set_precedence(OFF OFF)
elseif (DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset")
if (PKG_ARGS_BUNDLED_PACKAGE)
elseif(DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset")
if(PKG_ARGS_BUNDLED_PACKAGE)
set(local OFF)
else()
set(local ON)
@ -449,7 +451,7 @@ function(AddPackage)
set_precedence(ON OFF)
endif()
if (DEFINED PKG_ARGS_VERSION)
if(DEFINED PKG_ARGS_VERSION)
list(APPEND EXTRA_ARGS
VERSION ${PKG_ARGS_VERSION}
)
@ -475,32 +477,32 @@ function(AddPackage)
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)
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_GIT_VERSION)
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
${PKG_ARGS_GIT_VERSION})
elseif(DEFINED PKG_ARGS_TAG)
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
${PKG_ARGS_SHA})
elseif (DEFINED PKG_ARGS_GIT_VERSION)
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
${PKG_ARGS_GIT_VERSION})
elseif (DEFINED PKG_ARGS_TAG)
${PKG_ARGS_TAG})
elseif(DEFINED PKG_ARGS_VERSION)
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
${PKG_ARGS_TAG})
elseif(DEFINED PKG_ARGS_VERSION)
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
${PKG_ARGS_VERSION})
${PKG_ARGS_VERSION})
else()
cpm_utils_message(WARNING ${PKG_ARGS_NAME}
"Package has no specified sha, tag, or version")
"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
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)")
"${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION} (system)")
else()
set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS
"unknown (system)")
"unknown (system)")
endif()
endif()
@ -561,7 +563,7 @@ function(AddCIPackage)
message(FATAL_ERROR "[CPMUtil] PACKAGE is required")
endif()
if (NOT DEFINED PKG_ARGS_CMAKE_FILENAME)
if(NOT DEFINED PKG_ARGS_CMAKE_FILENAME)
set(ARTIFACT_CMAKE ${PKG_ARGS_NAME})
else()
set(ARTIFACT_CMAKE ${PKG_ARGS_CMAKE_FILENAME})
@ -573,11 +575,11 @@ function(AddCIPackage)
set(ARTIFACT_EXT ${PKG_ARGS_EXTENSION})
endif()
if (DEFINED PKG_ARGS_MIN_VERSION)
if(DEFINED PKG_ARGS_MIN_VERSION)
set(ARTIFACT_MIN_VERSION ${PKG_ARGS_MIN_VERSION})
endif()
if (DEFINED PKG_ARGS_DISABLED_PLATFORMS)
if(DEFINED PKG_ARGS_DISABLED_PLATFORMS)
set(DISABLED_PLATFORMS ${PKG_ARGS_DISABLED_PLATFORMS})
endif()
@ -587,19 +589,19 @@ function(AddCIPackage)
set(ARTIFACT_REPO ${PKG_ARGS_REPO})
set(ARTIFACT_PACKAGE ${PKG_ARGS_PACKAGE})
if ((MSVC AND ARCHITECTURE_x86_64) AND NOT "windows-amd64" IN_LIST DISABLED_PLATFORMS)
if((MSVC AND ARCHITECTURE_x86_64) AND NOT "windows-amd64" IN_LIST DISABLED_PLATFORMS)
add_ci_package(windows-amd64)
endif()
if ((MSVC AND ARCHITECTURE_arm64) AND NOT "windows-arm64" IN_LIST DISABLED_PLATFORMS)
if((MSVC AND ARCHITECTURE_arm64) AND NOT "windows-arm64" IN_LIST DISABLED_PLATFORMS)
add_ci_package(windows-arm64)
endif()
if ((MINGW AND ARCHITECTURE_x86_64) AND NOT "mingw-amd64" IN_LIST DISABLED_PLATFORMS)
if((MINGW AND ARCHITECTURE_x86_64) AND NOT "mingw-amd64" IN_LIST DISABLED_PLATFORMS)
add_ci_package(mingw-amd64)
endif()
if ((MINGW AND ARCHITECTURE_arm64) AND NOT "mingw-arm64" IN_LIST DISABLED_PLATFORMS)
if((MINGW AND ARCHITECTURE_arm64) AND NOT "mingw-arm64" IN_LIST DISABLED_PLATFORMS)
add_ci_package(mingw-arm64)
endif()
@ -628,11 +630,11 @@ function(AddCIPackage)
endif()
# TODO(crueter): macOS amd64/aarch64 split mayhaps
if (APPLE AND NOT "macos-universal" IN_LIST DISABLED_PLATFORMS)
if(APPLE AND NOT "macos-universal" IN_LIST DISABLED_PLATFORMS)
add_ci_package(macos-universal)
endif()
if (DEFINED ARTIFACT_DIR)
if(DEFINED ARTIFACT_DIR)
set(${ARTIFACT_PACKAGE}_ADDED TRUE PARENT_SCOPE)
set(${ARTIFACT_PACKAGE}_SOURCE_DIR "${ARTIFACT_DIR}" PARENT_SCOPE)
else()

17
docs/CPMUtil/AddCIPackage

@ -1,17 +0,0 @@
# AddPackage
- `VERSION` (required): The version to get (the tag will be `v${VERSION}`)
- `NAME` (required): Name used within the artifacts
- `REPO` (required): CI repository, e.g. `crueter-ci/OpenSSL`
- `PACKAGE` (required): `find_package` package name
- `EXTENSION`: Artifact extension (default `tar.zst`)
- `MIN_VERSION`: Minimum version for `find_package`. Only used if platform does not support this package as a bundled artifact
- `DISABLED_PLATFORMS`: List of platforms that lack artifacts for this package. Options:
* `windows-amd64`
* `windows-arm64`
* `android`
* `solaris-amd64`
* `freebsd-amd64`
* `linux-amd64`
* `linux-aarch64`
* `macos-universal`

19
docs/CPMUtil/README.md

@ -10,12 +10,13 @@ Global Options:
You are highly encouraged to read AddPackage first, even if you plan to only interact with CPMUtil via `AddJsonPackage`.
<!-- TOC -->
- [AddPackage](#addpackage)
- [AddCIPackage](#addcipackage)
- [AddJsonPackage](#addjsonpackage)
- [Lists](#lists)
<!-- /TOC -->
- [For Packagers](#for-packagers)
- [Network Sandbox](#network-sandbox)
- [Unsandboxed](#unsandboxed)
## AddPackage
@ -43,4 +44,16 @@ For an example of how this might be implemented in an application, see Eden's im
- [`dep_hashes.h.in`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/dep_hashes.h.in)
- [`GenerateDepHashes.cmake`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CMakeModules/GenerateDepHashes.cmake)
- [`deps_dialog.cpp`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/yuzu/deps_dialog.cpp)
- [`deps_dialog.cpp`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/yuzu/deps_dialog.cpp)
## For Packagers
If you are packaging a project that uses CPMUtil, read this!
### Network Sandbox
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!
### Unsandboxed
For others (AUR, MPR, etc). CPMUtil will handle everything for you, including if some of the project's dependencies are missing from your distribution's repositories. That is pretty much half the reason I created this behemoth, after all.

7
tools/cpm-fetch-all.sh

@ -8,10 +8,5 @@
# provided for workflow compat
# shellcheck disable=SC1091
. tools/cpm/common.sh
chmod +x tools/cpm/fetch.sh
# shellcheck disable=SC2086
tools/cpm/fetch.sh $LIBS
tools/cpmutil.sh package fetch -a

80
tools/cpm/README.md

@ -1,79 +1,3 @@
# CPMUtil Tools
# CPMUtil Tooling
These are supplemental shell scripts for CPMUtil aiming to ease maintenance burden for sanity checking, updates, prefetching, formatting, and standard operations done by these shell scripts, all in one common place.
All scripts are POSIX-compliant. If something doesn't work on your shell, ensure it's POSIX-compliant.
* If your shell doesn't support `$(...)` syntax, you've got bigger problems to worry about.
<!-- TOC -->
- [Meta](#meta)
- [Simple Utilities](#simple-utilities)
- [Functional Utilities](#functional-utilities)
<!-- /TOC -->
## Meta
These scripts are generally reserved for internal use.
- `common.sh`: Grabs all available cpmfiles and aggregates them together.
* Outputs:
- `PACKAGES`: The aggregated cpmfile
- `LIBS`: The list of individual libraries contained within each cpmfile
- `value`: A function that grabs a key from the `JSON` variable (typically the package key)
- `download.sh`: Utility script to handle downloading of regular and CI packages.
* Generally only used by the fetch scripts.
- `package.sh`: The actual package parser.
* Inputs:
- `PACKAGE`: The package key
* Outputs:
- Basically everything. You're best off reading the code rather than me poorly explaining it.
- `which.sh`: Find which cpmfile a package is located in.
* Inputs:
- The package key
- `replace.sh`: Replace a package's cpmfile definition.
* Inputs:
- `PACKAGE`: The package key
- `NEW_JSON`: All keys to replace/add
* Keys not found in the new json are not touched. Keys cannot currently be deleted.
## Simple Utilities
These scripts don't really have any functionality, they just help you out a bit yknow?
- `format.sh`: Format all cpmfiles (4-space indent is enforced)
* In the future, these scripts will have options for spacing
- `hash.sh`: Determine the hash of a specific package.
* Inputs:
- The repository (e.g. fmtlib/fmt)
- The sha or tag (e.g. v1.0.1)
- `-g <GIT_HOST>` or `--host <GIT_HOST>`: What git host to use (default github.com)
- `-a <ARTIFACT>` or `--artifact <ARTIFACT>`: The artifact to download. Set to null or empty to use a source archive instead
* Output: the SHA512 sum of the package
- `url-hash.sh`: Determine the hash of a URL
* Input: the URL
* Output: the SHA512 sum of the URL
## Functional Utilities
These modify the CPM cache or cpmfiles. Each allows you to input all the packages to act on, as well as a `<scriptname>-all.sh` that acts upon all available packages.
Beware: if a hash is `cf83e1357...` that means you got a 404 error!
- `fetch.sh`: Prefetch a package according to its cpmfile definition
* Packages are fetched to the `.cache/cpm` directory by default, following the CPMUtil default.
* Already-fetched packages will be skipped. You can invalidate the entire cache with `rm -rf .cache/cpm`, or invalidate a specific package with e.g. `rm -rf .cache/cpm/packagename` to force a refetch.
* In the future, a force option will be added
* Note that full prefetching will take a long time depending on your internet, the amount of dependencies, and the size of each dependency.
- `check-updates.sh`: Check a package for available updates
* This only applies to packages that utilize tags.
* If the tag is a format string, the `git_version` is acted upon instead.
* Specifying `-f` or `--force` will forcefully update the package and its hash, even if it's on on the latest version.
* Alternatively, only specify `-u` or `--update` to update packages that have new versions available.
* This script generally runs fast.
* Packages that should skip updates (e.g. older versions, OR packages with poorly-made tag structures... looking at you mbedtls) may specify `"skip_updates": true` in their cpmfile definition. This is unnecessary for untagged (e.g. sha or bare URL) packages.
- `check-hashes.sh`: Check a package's hash
* Specifying `-f` or `--force` will update the package's hash even if it's not mismatched.
* Alternatively, specify `-u` or `--update` to only fix mismatched hashes.
* This only applies to packages with hardcoded hashes, NOT ones that use hash URLs.
* This script will take a long time. This is operationally equivalent to a prefetch, and thus checking all hashes will take a while--but it's worth it! Just make sure you're not using dial-up.
You are recommended to run sanity hash checking for every pull request and commit, and weekly update checks.
CPMUtil's tooling entirely revolves around the `cpmutil.sh` script. It contains various functions to aid with package maintenance, such as sanity checks, updates, formatting, prefetching, adding/removing packages, and much more. These are now self-documenting, so view the scripts yourself or run the cpmutil script for help.

10
tools/cpm/check-hash-all.sh

@ -1,10 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
# shellcheck disable=SC2086
tools/cpm/check-hash.sh "$@" $LIBS

79
tools/cpm/check-hash.sh

@ -1,79 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
RETURN=0
usage() {
cat << EOF
Usage: $0 [uf] [PACKAGE]...
Check the hash of a specific package or packages.
If a hash mismatch occurs, this script will print the corrected hash of the package.
Options:
-u, --update Correct the package's hash if it's a mismatch
-f, --force Update the package's hash anyways (implies -u)
Note that this procedure will usually take a long time
depending on the number and size of dependencies.
This project has defined the following as valid cpmfiles:
EOF
for file in $CPMFILES; do
echo "- $file"
done
exit $RETURN
}
while true; do
case "$1" in
(-uf|-f|--force) UPDATE=true; FORCE=true; shift; continue ;;
(-u|--update) UPDATE=true; shift; continue ;;
(-h) usage ;;
("$0") break ;;
("") break ;;
esac
PACKAGE="$1"
shift
export PACKAGE
. tools/cpm/package.sh
if [ "$CI" != null ]; then
continue
fi
[ "$HASH_URL" != null ] && continue
[ "$HASH_SUFFIX" != null ] && continue
echo "-- Package $PACKAGE"
[ "$HASH" = null ] && echo "-- * Warning: no hash specified" && continue
export USE_TAG=true
ACTUAL=$(tools/cpm/url-hash.sh "$DOWNLOAD")
if [ "$ACTUAL" != "$HASH" ]; then
echo "-- * Expected $HASH"
echo "-- * Got $ACTUAL"
[ "$UPDATE" != "true" ] && RETURN=1
fi
if { [ "$UPDATE" = "true" ] && [ "$ACTUAL" != "$HASH" ]; } || [ "$FORCE" = "true" ]; then
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$ACTUAL\"")
export NEW_JSON
tools/cpm/replace.sh
fi
done
exit $RETURN

10
tools/cpm/check-updates-all.sh

@ -1,10 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
# shellcheck disable=SC2086
tools/cpm/check-updates.sh "$@" $LIBS

14
tools/cpm/common.sh

@ -1,22 +1,24 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
##################################
# CHANGE THESE FOR YOUR PROJECT! #
##################################
# TODO: cache cpmfile defs
# How many levels to go (3 is 2 subdirs max)
MAXDEPTH=3
# For your project you'll want to change this to define what dirs you have cpmfiles in
# Remember to account for the MAXDEPTH variable!
# Adding ./ before each will help to remove duplicates
[ -z "$CPMFILES" ] && CPMFILES=$(find . ./src -maxdepth "$MAXDEPTH" -name cpmfile.json | sort | uniq)
CPMFILES=$(find . -maxdepth "$MAXDEPTH" -name cpmfile.json | sort | uniq)
# shellcheck disable=SC2016
[ -z "$PACKAGES" ] && PACKAGES=$(echo "$CPMFILES" | xargs jq -s 'reduce .[] as $item ({}; . * $item)')
PACKAGES=$(echo "$CPMFILES" | xargs jq -s 'reduce .[] as $item ({}; . * $item)')
LIBS=$(echo "$PACKAGES" | jq -j 'keys_unsorted | join(" ")')
@ -25,7 +27,3 @@ export CPMFILES
export LIBS
export DIRS
export MAXDEPTH
value() {
echo "$JSON" | jq -r ".$1"
}

110
tools/cpm/download.sh

@ -1,110 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
download_package() {
FILENAME=$(basename "$DOWNLOAD")
OUTFILE="$TMP/$FILENAME"
LOWER_PACKAGE=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]')
OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}"
[ -d "$OUTDIR" ] && return
curl "$DOWNLOAD" -sS -L -o "$OUTFILE"
ACTUAL_HASH=$("${HASH_ALGO}"sum "$OUTFILE" | cut -d" " -f1)
[ "$ACTUAL_HASH" != "$HASH" ] && echo "!! $FILENAME did not match expected hash; expected $HASH but got $ACTUAL_HASH" && exit 1
TMPDIR="$TMP/extracted"
mkdir -p "$OUTDIR"
mkdir -p "$TMPDIR"
PREVDIR="$PWD"
mkdir -p "$TMPDIR"
cd "$TMPDIR"
case "$FILENAME" in
(*.7z)
7z x "$OUTFILE" > /dev/null
;;
(*.tar*)
tar xf "$OUTFILE" > /dev/null
;;
(*.zip)
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"
if echo "$JSON" | grep -e "patches" > /dev/null; then
PATCHES=$(echo "$JSON" | jq -r '.patches | join(" ")')
for patch in $PATCHES; do
# shellcheck disable=SC2154
patch --binary -p1 < "$ROOTDIR/.patch/$PACKAGE/$patch"
done
fi
cd "$PREVDIR"
}
ci_package() {
[ "$REPO" = null ] && echo "-- ! No repo defined" && return
echo "-- CI package $PACKAGE_NAME"
for platform in windows-amd64 windows-arm64 \
mingw-amd64 mingw-arm64 \
android-aarch64 android-x86_64 \
solaris-amd64 freebsd-amd64 openbsd-amd64 \
linux-amd64 linux-aarch64 \
macos-universal; 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
LOWER_PACKAGE=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]')
OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}"
[ -d "$OUTDIR" ] && continue
HASH_ALGO=$(value "hash_algo")
[ "$HASH_ALGO" = null ] && HASH_ALGO=sha512
HASH_SUFFIX="${HASH_ALGO}sum"
HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}"
HASH=$(curl "$HASH_URL" -sS -q -L -o -)
download_package
done
}

10
tools/cpm/fetch-all.sh

@ -1,10 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
# shellcheck disable=SC2086
tools/cpm/fetch.sh "$@" $LIBS

60
tools/cpm/fetch.sh

@ -1,60 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
[ -z "$CPM_SOURCE_CACHE" ] && CPM_SOURCE_CACHE=$PWD/.cache/cpm
mkdir -p "$CPM_SOURCE_CACHE"
# shellcheck disable=SC1091
. tools/cpm/common.sh
# shellcheck disable=SC1091
. tools/cpm/download.sh
# shellcheck disable=SC2034
ROOTDIR="$PWD"
TMP=$(mktemp -d)
usage() {
cat << EOF
Usage: $0 [PACKAGE]...
Fetch the specified package or packages from their defined download locations.
If the package is already cached, it will not be re-fetched.
This project has defined the following as valid cpmfiles:
EOF
for file in $CPMFILES; do
echo "- $file"
done
exit 0
}
while true; do
case "$1" in
(-h) usage ;;
("$0") break ;;
("") break ;;
esac
PACKAGE="$1"
shift
export PACKAGE
# shellcheck disable=SC1091
. tools/cpm/package.sh
if [ "$CI" = "true" ]; then
ci_package
else
echo "-- Downloading regular package $PACKAGE, with key $KEY, from $DOWNLOAD"
download_package
fi
done
rm -rf "$TMP"

11
tools/cpm/format.sh

@ -1,12 +1,9 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
for file in $CPMFILES; do
jq --indent 4 < "$file" > "$file".new
mv "$file".new "$file"
jq --indent 4 <"$file" >"$file".new
mv "$file".new "$file"
done

99
tools/cpm/hash.sh

@ -1,99 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# usage: hash.sh repo tag-or-sha
# env vars: GIT_HOST, USE_TAG (use tag instead of sha), ARTIFACT (download artifact with that name instead of src archive)
RETURN=0
usage() {
cat <<EOF
Usage: $0 [-a|--artifact ARTIFACT] [-g|--host GIT_HOST] [REPO] [REF]
Get the hash of a package.
REPO must be in the form of OWNER/REPO, and REF must be a commit sha, branch, or tag.
Options:
-g, --host <GIT_HOST> What Git host to use (defaults to github.com)
-a, --artifact <ARTIFACT> The artifact to download (implies -t)
If -t is specified but not -a, fetches a tag archive.
If ARTIFACT is specified but is null,
EOF
exit "$RETURN"
}
die() {
echo "$@" >&2
RETURN=1 usage
}
artifact() {
if [ $# -lt 2 ]; then
die "You must specify a valid artifact."
fi
shift
ARTIFACT="$1"
}
host() {
if [ $# -lt 2 ]; then
die "You must specify a valid Git host."
fi
shift
GIT_HOST="$1"
}
# this is a semi-hacky way to handle long/shortforms
while true; do
case "$1" in
-[a-z]*)
opt=$(echo "$1" | sed 's/^-//')
while [ -n "$opt" ]; do
# cut out first char from the optstring
char=$(echo "$opt" | cut -c1)
opt=$(echo "$opt" | cut -c2-)
case "$char" in
a) artifact "$@" ;;
g) host "$@" ;;
h) usage ;;
*) die "Invalid option -$char" ;;
esac
done
;;
--artifact) artifact "$@" ;;
--host) host "$@" ;;
--help) usage ;;
--*) die "Invalid option $1" ;;
"$0" | "") break ;;
*)
{ [ -z "$REPO" ] && REPO="$1"; } || REF="$1"
;;
esac
shift
done
[ -z "$REPO" ] && die "A valid repository must be provided."
[ -z "$REF" ] && die "A valid reference must be provided."
GIT_HOST=${GIT_HOST:-github.com}
GIT_URL="https://$GIT_HOST/$REPO"
if [ -z "$ARTIFACT" ] || [ "$ARTIFACT" = "null" ]; then
URL="${GIT_URL}/archive/$REF.tar.gz"
else
URL="${GIT_URL}/releases/download/$REF/$ARTIFACT"
fi
SUM=$(wget -q "$URL" -O - | sha512sum)
echo "$SUM" | cut -d " " -f1

47
tools/cpm/migrate.sh

@ -0,0 +1,47 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
SUBMODULES="$(git submodule status --recursive | cut -c2-)"
[ -z "$SUBMODULES" ] && echo "No submodules defined!" && exit 0
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 '()')
path=$(echo "$i" | cut -d" " -f2)
name=$(echo "$path" | awk -F/ '{print $NF}')
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,
git_version: $ver,
repo: $repo
} + (if $host != "" then {git_host: $host} else {} end)
}')
jq --argjson new "$entry" '. + $new' "$tmp" >"${tmp}.new"
mv "$tmp.new" "$tmp"
done
jq '.' "$tmp" >cpmfile.json
rm -f "$tmp"

276
tools/cpm/package.sh

@ -1,200 +1,80 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
[ -z "$PACKAGE" ] && echo "Package was not specified" && exit 0
# shellcheck disable=SC2153
JSON=$(echo "$PACKAGES" | jq -r ".\"$PACKAGE\" | select( . != null )")
[ -z "$JSON" ] && echo "!! No cpmfile definition for $PACKAGE" >&2 && exit 1
# 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 GIT_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"
export HASH_URL="null"
export HASH_SUFFIX="null"
export HASH_ALGO="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
export PACKAGE_NAME
export REPO
export CI
export GIT_HOST
######################
# CI Package Parsing #
######################
VERSION=$(value "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
export VERSION
return 0
fi
##############
# Versioning #
##############
TAG=$(value "tag")
ARTIFACT=$(value "artifact")
SHA=$(value "sha")
GIT_VERSION=$(value "git_version")
[ "$GIT_VERSION" = null ] && GIT_VERSION="$VERSION"
if [ "$GIT_VERSION" != null ]; then
VERSION_REPLACE="$GIT_VERSION"
else
VERSION_REPLACE="$VERSION"
fi
echo "$TAG" | grep -e "%VERSION%" > /dev/null && HAS_REPLACE=true || HAS_REPLACE=false
ORIGINAL_TAG="$TAG"
TAG=$(echo "$TAG" | sed "s/%VERSION%/$VERSION_REPLACE/g")
ARTIFACT=$(echo "$ARTIFACT" | sed "s/%VERSION%/$VERSION_REPLACE/g")
ARTIFACT=$(echo "$ARTIFACT" | sed "s/%TAG%/$TAG/g")
export TAG
export ARTIFACT
export SHA
export VERSION
export GIT_VERSION
export ORIGINAL_TAG
export HAS_REPLACE
export VERSION_REPLACE
###############
# URL Parsing #
###############
URL=$(value "url")
if [ "$URL" != "null" ]; then
DOWNLOAD="$URL"
elif [ "$REPO" != "null" ]; then
GIT_URL="https://$GIT_HOST/$REPO"
BRANCH=$(value "branch")
if [ "$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
elif [ "$SHA" != "null" ]; then
DOWNLOAD="${GIT_URL}/archive/${SHA}.tar.gz"
else
if [ "$BRANCH" = null ]; then
BRANCH=master
fi
DOWNLOAD="${GIT_URL}/archive/refs/heads/${BRANCH}.tar.gz"
fi
else
echo "!! No repo or URL defined for $PACKAGE"
exit 1
fi
export DOWNLOAD
export URL
###############
# Key Parsing #
###############
KEY=$(value "key")
if [ "$KEY" = null ]; then
if [ "$SHA" != null ]; then
KEY=$(echo "$SHA" | cut -c1-4)
elif [ "$GIT_VERSION" != null ]; then
KEY="$GIT_VERSION"
elif [ "$TAG" != null ]; then
KEY="$TAG"
elif [ "$VERSION" != null ]; then
KEY="$VERSION"
else
echo "!! No valid key could be determined for $PACKAGE. Must define one of: key, sha, tag, version, git_version"
exit 1
fi
fi
export KEY
################
# Hash Parsing #
################
HASH_ALGO=$(value "hash_algo")
[ "$HASH_ALGO" = null ] && HASH_ALGO=sha512
HASH=$(value "hash")
if [ "$HASH" = null ]; then
HASH_SUFFIX="${HASH_ALGO}sum"
HASH_URL=$(value "hash_url")
if [ "$HASH_URL" = null ]; then
HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}"
fi
HASH=$(curl "$HASH_URL" -Ss -L -o -)
else
HASH_URL=null
HASH_SUFFIX=null
fi
export HASH_URL
export HASH_SUFFIX
export HASH
export HASH_ALGO
export JSON
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
RETURN=0
usage() {
cat <<EOF
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 Find which cpmfile a package is defined in
download Get the download URL for a package
EOF
exit $RETURN
}
SCRIPTS=$(CDPATH='' cd -- "$(dirname -- "$0")/package" && pwd)
export SCRIPTS
while :; do
case "$1" in
hash)
shift
"$SCRIPTS"/hash.sh "$@"
break
;;
update)
shift
"$SCRIPTS"/update.sh "$@"
break
;;
fetch)
shift
"$SCRIPTS"/fetch.sh "$@"
break
;;
add)
shift
"$SCRIPTS"/add.sh "$@"
break
;;
rm)
shift
"$SCRIPTS"/rm.sh "$@"
break
;;
version)
shift
"$SCRIPTS"/version.sh "$@"
break
;;
which)
shift
"$SCRIPTS"/which.sh "$@"
break
;;
download)
shift
"$SCRIPTS"/download.sh "$@"
break
;;
-h | --help) usage ;;
"") usage ;;
*) usage ;;
esac
shift
done

79
tools/cpm/package/add.sh

@ -0,0 +1,79 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
RETURN=0
usage() {
cat <<EOF
Usage: cpmutil.sh package add [-s|--sha] [-t|--tag]
[-c|--cpmfile CPMFILE] [package name]
Add a new package to a cpmfile.
Options:
-t, --tag Use tag versioning, instead of the default,
commit sha versioning.
-c, --cpmfile <CPMFILE> Use the specified cpmfile instead of the root cpmfile
Note that you are still responsible for integrating this into your CMake.
EOF
exit $RETURN
}
die() {
echo "-- $*" >&2
exit 1
}
_cpmfile() {
[ -z "$1" ] && die "You must specify a valid cpmfile."
CPMFILE="$1"
}
while :; do
case "$1" in
-[a-z]*)
opt=$(printf '%s' "$1" | sed 's/^-//')
while [ -n "$opt" ]; do
# cut out first char from the optstring
char=$(echo "$opt" | cut -c1)
opt=$(echo "$opt" | cut -c2-)
case "$char" in
t) TAG=1 ;;
c)
_cpmfile "$2"
shift
;;
h) usage ;;
*) die "Invalid option -$char" ;;
esac
done
;;
--tag) TAG=1 ;;
--cpmfile)
_cpmfile "$2"
shift
;;
--help) usage ;;
"$0") break ;;
"") break ;;
*) PKG="$1" ;;
esac
shift
done
: "${CPMFILE:=$PWD/cpmfile.json}"
[ -z "$PKG" ] && die "You must specify a package name."
export PKG
export CPMFILE
export TAG
"$SCRIPTS"/util/interactive.sh

46
tools/cpm/package/download.sh

@ -0,0 +1,46 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
usage() {
cat <<EOF
Usage: cpmutil.sh package download [-a|--all] [PACKAGE]...
Get the download URL for the specified packages.
Options:
-a, --all Operate on all packages in this project.
EOF
exit 0
}
while :; do
case "$1" in
-a | --all) ALL=1 ;;
-h | --help) usage ;;
"$0") break ;;
"") break ;;
*) packages="$packages $1" ;;
esac
shift
done
[ "$ALL" = 1 ] && packages="${LIBS:-$packages}"
[ -z "$packages" ] && usage
for pkg in $packages; do
PACKAGE="$pkg"
export PACKAGE
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
if [ "$CI" = "true" ]; then
echo "-- $PACKAGE: https://$GIT_HOST/$REPO"
else
echo -- "$PACKAGE: $DOWNLOAD"
fi
done

156
tools/cpm/package/fetch.sh

@ -0,0 +1,156 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
: "${CPM_SOURCE_CACHE:=$PWD/.cache/cpm}"
mkdir -p "$CPM_SOURCE_CACHE"
ROOTDIR="$PWD"
TMP=$(mktemp -d)
download_package() {
FILENAME=$(basename "$DOWNLOAD")
OUTFILE="$TMP/$FILENAME"
LOWER_PACKAGE=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]')
OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}"
[ -d "$OUTDIR" ] && return
curl "$DOWNLOAD" -sS -L -o "$OUTFILE"
ACTUAL_HASH=$("${HASH_ALGO}"sum "$OUTFILE" | cut -d" " -f1)
[ "$ACTUAL_HASH" != "$HASH" ] && echo "!! $FILENAME did not match expected hash; expected $HASH but got $ACTUAL_HASH" && exit 1
TMPDIR="$TMP/extracted"
mkdir -p "$OUTDIR"
mkdir -p "$TMPDIR"
PREVDIR="$PWD"
mkdir -p "$TMPDIR"
cd "$TMPDIR"
case "$FILENAME" in
*.7z)
7z x "$OUTFILE" >/dev/null
;;
*.tar*)
tar xf "$OUTFILE" >/dev/null
;;
*.zip)
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"
if echo "$JSON" | grep -e "patches" >/dev/null; then
PATCHES=$(echo "$JSON" | jq -r '.patches | join(" ")')
for patch in $PATCHES; do
patch --binary -p1 <"$ROOTDIR/.patch/$PACKAGE/$patch"
done
fi
cd "$PREVDIR"
}
ci_package() {
[ "$REPO" = null ] && echo "-- ! No repo defined" && return
echo "-- CI package $PACKAGE_NAME"
for platform in windows-amd64 windows-arm64 \
mingw-amd64 mingw-arm64 \
android-aarch64 android-x86_64 \
solaris-amd64 freebsd-amd64 openbsd-amd64 \
linux-amd64 linux-aarch64 \
macos-universal; 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"
LOWER_PACKAGE=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]')
OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}"
[ -d "$OUTDIR" ] && continue
HASH_ALGO=$(echo "$JSON" | jq -r ".hash_algo")
[ "$HASH_ALGO" = null ] && HASH_ALGO=sha512
HASH_SUFFIX="${HASH_ALGO}sum"
HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}"
HASH=$(curl "$HASH_URL" -sS -q -L -o -)
download_package
done
}
usage() {
cat <<EOF
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.
EOF
exit 0
}
while :; do
case "$1" in
-h | --help) usage ;;
-a | --all) ALL=1 ;;
"$0") break ;;
"") break ;;
*) packages="$packages $1" ;;
esac
shift
done
[ "$ALL" = 1 ] && packages="${LIBS:-$packages}"
[ -z "$packages" ] && usage
for PACKAGE in $packages; do
export PACKAGE
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
if [ "$CI" = "true" ]; then
ci_package
else
echo "-- Downloading regular package $PACKAGE, with key $KEY, from $DOWNLOAD"
download_package
fi
done
rm -rf "$TMP"

66
tools/cpm/package/hash.sh

@ -0,0 +1,66 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
RETURN=0
usage() {
cat <<EOF
Usage: cpmutil.sh package hash [-n|--dry-run] [-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:
-n, --dry-run Don't update the package's hash if it's a mismatch
-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.
EOF
exit $RETURN
}
while :; do
case "$1" in
-[a-z]*)
opt=$(printf '%s' "$1" | sed 's/^-//')
while [ -n "$opt" ]; do
# cut out first char from the optstring
char=$(echo "$opt" | cut -c1)
opt=$(echo "$opt" | cut -c2-)
case "$char" in
a) ALL=1 ;;
n) DRY=1 ;;
h) usage ;;
*) die "Invalid option -$char" ;;
esac
done
;;
--dry-run) DRY=1 ;;
--all) ALL=1 ;;
--help) usage ;;
"$0") break ;;
"") break ;;
*) packages="$packages $1" ;;
esac
shift
done
[ "$ALL" = 1 ] && packages="${LIBS:-$packages}"
[ "$DRY" = 1 ] && UPDATE=false || UPDATE=true
[ -z "$packages" ] && usage
export UPDATE
for pkg in $packages; do
echo "-- Package $pkg"
"$SCRIPTS"/util/fix-hash.sh "$pkg" || RETURN=1
done
exit $RETURN

30
tools/cpm/package/rm.sh

@ -0,0 +1,30 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
RETURN=0
usage() {
cat <<EOF
Usage: cpmutil.sh package rm [PACKAGE]...
Delete a package or packages' cpmfile definition(s).
EOF
exit $RETURN
}
[ $# -lt 1 ] && usage
for pkg in "$@"; do
JSON=$("$SCRIPTS"/which.sh "$pkg") || {
echo "!! No cpmfile definition for $pkg"
continue
}
jq --indent 4 "del(.\"$pkg\")" "$JSON" >"$JSON".tmp
mv "$JSON".tmp "$JSON"
echo "-- Removed $pkg from $JSON" || :
done

96
tools/cpm/check-updates.sh → tools/cpm/package/update.sh

@ -1,12 +1,7 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# shellcheck disable=SC1091
. tools/cpm/common.sh
RETURN=0
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
filter_out() {
TAGS=$(echo "$TAGS" | jq "[.[] | select(.name | test(\"$1\"; \"i\") | not)]")
@ -17,53 +12,57 @@ filter_in() {
}
usage() {
cat << EOF
Usage: $0 [uf] [PACKAGE]...
cat <<EOF
Usage: cpmutil.sh package update [-n|--dry-run] [-a|--all] [PACKAGE]...
Check a specific package or packages for updates.
Options:
-u, --update Update the package if a new version is available.
This will also update the hash if provided.
-f, --force Forcefully update the package version (implies -u)
This is seldom useful, and should only be used in cases of
severe corruption.
-n, --dry-run Do not update the package if it has an update available
-a, --all Operate on all packages in this project.
This project has defined the following as valid cpmfiles:
EOF
for file in $CPMFILES; do
echo "- $file"
done
exit $RETURN
exit 0
}
while true; do
while :; do
case "$1" in
-f | --force)
UPDATE=true
FORCE=true
shift
continue
;;
-u | --update)
UPDATE=true
shift
continue
-[a-z]*)
opt=$(printf '%s' "$1" | sed 's/^-//')
while [ -n "$opt" ]; do
# cut out first char from the optstring
char=$(echo "$opt" | cut -c1)
opt=$(echo "$opt" | cut -c2-)
case "$char" in
a) ALL=1 ;;
n) DRY=1 ;;
h) usage ;;
*) die "Invalid option -$char" ;;
esac
done
;;
-h) usage ;;
--dry-run) DRY=1 ;;
--all) ALL=1 ;;
--help) usage ;;
"$0") break ;;
"") break ;;
*) packages="$packages $1" ;;
esac
PACKAGE="$1"
shift
done
[ "$ALL" = 1 ] && packages="${LIBS:-$packages}"
[ "$DRY" = 1 ] && UPDATE=false || UPDATE=true
[ -z "$packages" ] && usage
for pkg in $packages; do
PACKAGE="$pkg"
export PACKAGE
# shellcheck disable=SC1091
. tools/cpm/package.sh
. "$SCRIPTS"/vars.sh
SKIP=$(value "skip_updates")
@ -72,10 +71,9 @@ while true; do
[ "$REPO" = null ] && continue
[ "$GIT_HOST" != "github.com" ] && continue # TODO
if [ "$CI" = "true" ]; then
TAG="v$VERSION"
fi
[ "$CI" = "true" ] && continue
# shellcheck disable=SC2153
[ "$TAG" = null ] && continue
echo "-- Package $PACKAGE"
@ -93,8 +91,6 @@ while true; do
filter_out vulkan-sdk
fi
[ "$CI" = "true" ] && filter_in "-"
filter_out yotta # mbedtls
# ignore betas/alphas (remove if needed)
@ -110,8 +106,6 @@ while true; do
[ "$LATEST" = "null" ] && echo "-- * Up-to-date" && continue
[ "$LATEST" = "$TAG" ] && [ "$FORCE" != "true" ] && echo "-- * Up-to-date" && continue
RETURN=1
if [ "$HAS_REPLACE" = "true" ]; then
# this just extracts the tag prefix
VERSION_PREFIX=$(echo "$ORIGINAL_TAG" | cut -d"%" -f1)
@ -126,23 +120,15 @@ while true; do
echo "-- * Version $LATEST available, current is $TAG"
HASH=$(tools/cpm/hash.sh "$REPO" "$LATEST")
echo "-- * New hash: $HASH"
if [ "$UPDATE" = "true" ]; then
RETURN=0
if [ "$HAS_REPLACE" = "true" ]; then
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$HASH\" | .git_version = \"$NEW_GIT_VERSION\"")
NEW_JSON=$(echo "$JSON" | jq ".git_version = \"$NEW_GIT_VERSION\"")
else
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$HASH\" | .tag = \"$LATEST\"")
NEW_JSON=$(echo "$JSON" | jq ".tag = \"$LATEST\"")
fi
export NEW_JSON
"$SCRIPTS"/util/replace.sh "$PACKAGE" "$NEW_JSON"
tools/cpm/replace.sh
QUIET=true "$SCRIPTS"/util/fix-hash.sh
fi
done
exit $RETURN

30
tools/cpm/package/util/fix-hash.sh

@ -0,0 +1,30 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
: "${PACKAGE:=$1}"
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
[ "$CI" = null ] || exit 0
[ "$HASH_URL" = null ] || exit 0
[ "$HASH_SUFFIX" = null ] || exit 0
[ "$HASH" = null ] && echo "-- * Package has no hash specified" && 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

217
tools/cpm/package/util/interactive.sh

@ -0,0 +1,217 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 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"
[ -n "$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 "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 "Git host (default: github.com)" \
"The hostname of the Git server, if not GitHub (e.g. codeberg.org, git.crueter.xyz)"
GIT_HOST="$reply"
if [ "$TAG" = "1" ]; then
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%."
GIT_VERSION="$reply"
optional "Name of the upstream tag. %VERSION% is replaced by the numeric version (default: %VERSION%)" \
"Most commonly this will be something like v%VERSION% or release-%VERSION%, or just %VERSION%."
TAGNAME="$reply"
[ -z "$TAGNAME" ] && TAGNAME="%VERSION%"
optional "Name of the release artifact to download, if applicable.
-- %VERSION% is replaced by the numeric 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 "Version of the CI package (e.g. 3.6.0-9eff87adb1)" \
"CI artifacts are stored as <name>-<platform>-<version>.tar.zst. This option controls the version."
VERSION="$reply"
required "Name of the CI artifact" \
"CI artifacts are stored as <name>-<platform>-<version>.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
solaris-amd64 freebsd-amd64 openbsd-amd64
linux-amd64 linux-aarch64
macos-universal"
DISABLED_PLATFORMS="$reply"
fi
# now time to construct the actual json
jq_input='{repo: "'"$REPO"'"}'
# common trivial fields
[ -n "$PACKAGE" ] && jq_input="$jq_input + {package: \"$PACKAGE\"}"
[ -n "$MIN_VERSION" ] && jq_input="$jq_input + {min_version: \"$MIN_VERSION\"}"
[ -n "$FIND_ARGS" ] && jq_input="$jq_input + {find_args: \"$FIND_ARGS\"}"
if [ "$CI" = "true" ]; then
jq_input="$jq_input + {
ci: true,
version: \"$VERSION\",
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
[ -n "$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
# Git host
if [ -n "$GIT_HOST" ] && [ "$GIT_HOST" != "github.com" ]; then
jq_input="$jq_input + {git_host: \"$GIT_HOST\"}"
fi
# versioning stuff
if [ -n "$GIT_VERSION" ]; then
jq_input="$jq_input + {git_version: \"$GIT_VERSION\"}"
[ -n "$TAGNAME" ] && jq_input="$jq_input + {tag: \"$TAGNAME\"}"
[ -n "$ARTIFACT" ] && jq_input="$jq_input + {artifact: \"$ARTIFACT\"}"
else
jq_input="$jq_input + {sha: \"$SHA\"}"
fi
fi
new_json=$(jq -n "$jq_input")
jq --arg key "$PKG" --argjson new "$new_json" \
'.[$key] = $new' "$CPMFILE" --indent 4 >"${CPMFILE}.tmp" &&
mv "${CPMFILE}.tmp" "$CPMFILE"
# now correct the hash
if [ "$CI" != true ]; then
# shellcheck disable=SC1091
. "$ROOTDIR"/common.sh
QUIET=true UPDATE=true "$SCRIPTS"/util/fix-hash.sh "$PKG"
fi
echo "Added package $PKG to $CPMFILE. Include it in your project with AddJsonPackage($PKG)"

13
tools/cpm/package/util/replace.sh

@ -0,0 +1,13 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# Replace a specified package with a modified json.
FILE=$(echo "$CPMFILES" | xargs grep -l "\"$1\"")
jq --indent 4 --argjson repl "$2" ".\"$1\" *= \$repl" "$FILE" >"$FILE".new
mv "$FILE".new "$FILE"
echo "-- * -- Updated $FILE"

7
tools/cpm/package/util/url-hash.sh

@ -0,0 +1,7 @@
#!/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

170
tools/cpm/package/vars.sh

@ -0,0 +1,170 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# shellcheck disable=SC1091
value() {
echo "$JSON" | jq -r ".$1"
}
[ -z "$PACKAGE" ] && echo "Package was not specified" && exit 0
# shellcheck disable=SC2153
JSON=$(echo "$PACKAGES" | jq -r ".\"$PACKAGE\" | select( . != null )")
[ -z "$JSON" ] && echo "!! No cpmfile definition for $PACKAGE" >&2 && exit 1
# 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 GIT_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"
export HASH_URL="null"
export HASH_SUFFIX="null"
export HASH_ALGO="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
export PACKAGE_NAME
export REPO
export CI
export GIT_HOST
######################
# CI Package Parsing #
######################
VERSION=$(value "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
export VERSION
return 0
fi
##############
# Versioning #
##############
TAG=$(value "tag")
ARTIFACT=$(value "artifact")
SHA=$(value "sha")
GIT_VERSION=$(value "git_version")
[ "$GIT_VERSION" = null ] && GIT_VERSION="$VERSION"
if [ "$GIT_VERSION" != null ]; then
VERSION_REPLACE="$GIT_VERSION"
else
VERSION_REPLACE="$VERSION"
fi
echo "$TAG" | grep -e "%VERSION%" >/dev/null &&
HAS_REPLACE=true || HAS_REPLACE=false
ORIGINAL_TAG="$TAG"
TAG=$(echo "$TAG" | sed "s/%VERSION%/$VERSION_REPLACE/g")
ARTIFACT=$(echo "$ARTIFACT" | sed "s/%VERSION%/$VERSION_REPLACE/g")
ARTIFACT=$(echo "$ARTIFACT" | sed "s/%TAG%/$TAG/g")
export TAG
export ARTIFACT
export SHA
export VERSION
export GIT_VERSION
export ORIGINAL_TAG
export HAS_REPLACE
export VERSION_REPLACE
###############
# URL Parsing #
###############
URL=$(value "url")
BRANCH=$(value "branch")
export BRANCH
export URL
. "$SCRIPTS"/vars/url.sh
export DOWNLOAD
###############
# Key Parsing #
###############
KEY=$(value "key")
. "$SCRIPTS"/vars/key.sh
export KEY
################
# Hash Parsing #
################
HASH_ALGO=$(value "hash_algo")
[ "$HASH_ALGO" = null ] && HASH_ALGO=sha512
HASH=$(value "hash")
if [ "$HASH" = null ]; then
HASH_SUFFIX="${HASH_ALGO}sum"
HASH_URL=$(value "hash_url")
if [ "$HASH_URL" = null ]; then
HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}"
fi
HASH=$(curl "$HASH_URL" -Ss -L -o -)
else
HASH_URL=null
HASH_SUFFIX=null
fi
export HASH_URL
export HASH_SUFFIX
export HASH
export HASH_ALGO
export JSON

21
tools/cpm/package/vars/key.sh

@ -0,0 +1,21 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
if [ "$KEY" = null ]; then
if [ "$SHA" != null ]; then
KEY=$(echo "$SHA" | cut -c1-4)
elif [ "$GIT_VERSION" != null ]; then
KEY="$GIT_VERSION"
elif [ "$TAG" != null ]; then
KEY="$TAG"
elif [ "$VERSION" != null ]; then
KEY="$VERSION"
else
echo "!! No valid key could be determined for $PACKAGE_NAME. Must define one of: key, sha, tag, version, git_version"
exit 1
fi
fi
export KEY

32
tools/cpm/package/vars/url.sh

@ -0,0 +1,32 @@
#!/bin/sh
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# Required vars: URL, GIT_HOST, REPO, TAG, ARTIFACT, BRANCH, SHA
if [ "$URL" != "null" ]; then
DOWNLOAD="$URL"
elif [ "$REPO" != "null" ]; then
GIT_URL="https://$GIT_HOST/$REPO"
if [ "$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
elif [ "$SHA" != "null" ]; then
DOWNLOAD="${GIT_URL}/archive/${SHA}.tar.gz"
else
if [ "$BRANCH" = null ]; then
BRANCH=master
fi
DOWNLOAD="${GIT_URL}/archive/refs/heads/${BRANCH}.tar.gz"
fi
else
echo "!! No repo or URL defined for $PACKAGE_NAME"
exit 1
fi
export DOWNLOAD

60
tools/cpm/package/version.sh

@ -0,0 +1,60 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# shellcheck disable=SC1091
usage() {
cat <<EOF
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.
EOF
exit 0
}
PACKAGE="$1"
NEW_VERSION="$2"
[ -z "$PACKAGE" ] && usage
[ -z "$NEW_VERSION" ] && usage
export PACKAGE
. "$SCRIPTS"/vars.sh
[ "$REPO" = null ] && exit 0
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 git_version
if [ -z "$VERSION_PREFIX" ]; then
NEW_GIT_VERSION="$NEW_VERSION"
else
NEW_GIT_VERSION=$(echo "$NEW_VERSION" | sed "s/$VERSION_PREFIX//g")
fi
fi
if [ "$SHA" != null ]; then
NEW_JSON=$(echo "$JSON" | jq ".sha = \"$NEW_VERSION\"")
elif [ "$CI" = "true" ]; then
NEW_JSON=$(echo "$JSON" | jq ".version = \"$NEW_VERSION\"")
elif [ "$HAS_REPLACE" = "true" ]; then
NEW_JSON=$(echo "$JSON" | jq ".git_version = \"$NEW_GIT_VERSION\"")
else
NEW_JSON=$(echo "$JSON" | jq ".tag = \"$NEW_VERSION\"")
fi
echo "-- * -- Updating $PACKAGE to version $NEW_VERSION"
"$SCRIPTS"/util/replace.sh "$PACKAGE" "$NEW_JSON"
[ "$CI" = "true" ] && exit 0
echo "-- * -- Fixing hash"
. "$ROOTDIR"/common.sh
UPDATE=true QUIET=true "$SCRIPTS"/util/fix-hash.sh

12
tools/cpm/package/which.sh

@ -0,0 +1,12 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# check which file a package is in
JSON=$(echo "$CPMFILES" | xargs grep -l "\"$1\"")
[ -z "$JSON" ] && echo "!! No cpmfile definition for $1" >&2 && exit 1
echo "$JSON"

20
tools/cpm/replace.sh

@ -1,20 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# Replace a specified package with a modified json.
# env vars:
# - PACKAGE: The package key to act on
# - NEW_JSON: The new json to use
[ -z "$PACKAGE" ] && echo "You must provide the PACKAGE environment variable." && return 1
[ -z "$NEW_JSON" ] && echo "You must provide the NEW_JSON environment variable." && return 1
FILE=$(tools/cpm/which.sh "$PACKAGE")
jq --indent 4 --argjson repl "$NEW_JSON" ".\"$PACKAGE\" *= \$repl" "$FILE" > "$FILE".new
mv "$FILE".new "$FILE"
echo "-- * -- Updated $FILE"

8
tools/update-cpm.sh → tools/cpm/update.sh

@ -1,14 +1,14 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-License-Identifier: LGPL-3.0-or-later
# updates CPMUtil, its docs, and related tools from the latest release
if command -v zstd > /dev/null; then
EXT=tar.zst
if command -v zstd >/dev/null; then
EXT=tar.zst
else
EXT=tar.gz
EXT=tar.gz
fi
wget "https://git.crueter.xyz/CMake/CPMUtil/releases/download/continuous/CPMUtil.$EXT"

7
tools/cpm/url-hash.sh

@ -1,7 +0,0 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
SUM=$(wget -q "$1" -O - | sha512sum)
echo "$SUM" | cut -d " " -f1

15
tools/cpm/which.sh

@ -1,15 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
# check which file a package is in
# shellcheck disable=SC1091
. tools/cpm/common.sh
# shellcheck disable=SC2086
JSON=$(echo "$CPMFILES" | xargs grep -l "$1")
[ -z "$JSON" ] && echo "!! No cpmfile definition for $1" >&2 && exit 1
echo "$JSON"

78
tools/cpmutil.sh

@ -0,0 +1,78 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# shellcheck disable=SC1091
ROOTDIR=$(CDPATH='' cd -- "$(dirname -- "$0")/cpm" && pwd)
SCRIPTS="$ROOTDIR"
. "$SCRIPTS"/common.sh
RETURN=0
die() {
echo "-- $*" >&2
exit 1
}
usage() {
cat <<EOF
Usage: $0 [command]
General command-line utility for CPMUtil operations.
Commands:
package Run operations on a package or packages
format Format all cpmfiles
update Update CPMUtil and its tooling
ls List all cpmfiles
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 Find which cpmfile a package is defined in
download Get the download URL for a package
EOF
exit $RETURN
}
export ROOTDIR
while :; do
case "$1" in
-h | --help) usage ;;
ls)
echo "$CPMFILES" | tr ' ' '\n'
break
;;
format)
"$SCRIPTS"/format.sh
break
;;
update)
"$SCRIPTS"/update.sh
break
;;
migrate)
"$SCRIPTS"/migrate.sh
break
;;
package)
shift
"$SCRIPTS"/package.sh "$@"
break
;;
*) usage ;;
esac
shift
done
Loading…
Cancel
Save