[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 <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4130
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
crueter
2026-07-06 23:11:39 +02:00
parent 4c65780f11
commit 66b073ab8d
45 changed files with 2306 additions and 2992 deletions
+149 -2
View File
@@ -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
}
"$SCRIPTS"/util/interactive.sh
# 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 <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
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
echo "Added package $PKG to cpmfile.json. Include it in your project with AddJsonPackage($PKG)"
-57
View File
@@ -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 <<EOF
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.
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}"
[ -n "$packages" ] || usage
for pkg in $packages; do
unset JSON
export PACKAGE="$pkg"
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
# TODO: common get dir func
if [ "$CI" = true ]; then
dir="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}"
else
dir="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}"
fi
echo "-- $pkg: $dir"
if [ ! -d "$dir" ]; then
echo "-- * Warning: directory does not exist. Use fetch or reset to create it"
fi
done
-47
View File
@@ -1,47 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2026 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}"
[ -n "$packages" ] || usage
for pkg in $packages; do
unset JSON
export PACKAGE="$pkg"
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
if [ "$CI" = "true" ]; then
echo "-- $PACKAGE: https://$GIT_HOST/$REPO"
else
echo -- "$PACKAGE: $DOWNLOAD"
fi
done
-44
View File
@@ -1,44 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2026 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# shellcheck disable=SC1091
. "$SCRIPTS/util/fetch.sh"
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}"
[ -n "$packages" ] || usage
for PACKAGE in $packages; do
unset JSON
export PACKAGE
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
fetch_package
done
-91
View File
@@ -1,91 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2026 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
[ -n "$packages" ] || usage
export UPDATE
for pkg in $packages; do
unset JSON
echo "-- Package $pkg"
export PACKAGE="$pkg"
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
[ "$CI" = null ] || continue
ACTUAL=$("$SCRIPTS"/util/url-hash.sh "$DOWNLOAD")
if [ "$ACTUAL" != "$HASH" ] && [ "$QUIET" != true ]; then
echo "-- * Expected $HASH"
echo "-- * Got $ACTUAL"
if [ "$UPDATE" != "true" ]; then
RETURN=1
continue
fi
fi
if [ "$UPDATE" = "true" ] && [ "$ACTUAL" != "$HASH" ]; then
NEW_JSON=$(echo "$JSON" | jq ".hash = \"$ACTUAL\"")
"$SCRIPTS"/util/replace.sh "$PACKAGE" "$NEW_JSON"
fi
done
exit $RETURN
+6 -163
View File
@@ -4,170 +4,13 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# shellcheck disable=SC1091
. "$SCRIPTS"/../common.sh
RETURN=0
usage() {
cat <<EOF
Usage: cpmutil.sh package patch [PACKAGE]
Create an in-tree patch for the specified package.
EOF
exit "$RETURN"
}
die() {
echo "-- $*" >&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 "$@"
-56
View File
@@ -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 <<EOF
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.
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}"
[ -n "$packages" ] || usage
for PACKAGE in $packages; do
unset JSON
export PACKAGE
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
if [ "$CI" = true ]; then
dir="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}"
else
dir="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}"
fi
echo "-- Removing $dir"
rm -rf "$dir"
# shellcheck disable=SC1091
. "$SCRIPTS"/util/fetch.sh
fetch_package
done
-30
View File
@@ -1,30 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2026 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.
EOF
exit $RETURN
}
[ $# -ge 1 ] || usage
for pkg in "$@"; do
"$SCRIPTS"/which.sh "$pkg" || {
echo "!! No cpmfile definition for $pkg"
continue
}
jq --indent 4 "del(.\"$pkg\")" cpmfile.json >cpmfile.json.new
mv cpmfile.json.new cpmfile.json
echo "-- Removed $pkg"
done
-174
View File
@@ -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 <<EOF
Usage: cpmutil.sh package update [-n|--dry-run] [-a|--all] [PACKAGE]...
Check a specific package or packages for updates.
Options:
-n, --dry-run Do not update the package if it has an update available
-a, --all Operate on all packages in this project.
-c, --commit Automatically generate a commit message
EOF
exit 0
}
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) UPDATE=false ;;
c) COMMIT=true ;;
h) usage ;;
*) die "Invalid option -$char" ;;
esac
done
;;
--dry-run) UPDATE=false ;;
--all) ALL=1 ;;
--help) usage ;;
--commit) COMMIT=true ;;
"$0") break ;;
"") break ;;
*) packages="$packages $1" ;;
esac
shift
done
[ "$ALL" != 1 ] || packages="${LIBS:-$packages}"
: "${UPDATE:=true}"
: "${COMMIT:=false}"
[ -n "$packages" ] || usage
for pkg in $packages; do
unset JSON
export PACKAGE="$pkg"
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
SKIP=$(value "skip_updates")
[ "$SKIP" != "true" ] || continue
[ "$REPO" != null ] || continue
[ "$GIT_HOST" = "github.com" ] || continue # TODO
[ "$CI" != "true" ] || continue
# shellcheck disable=SC2153
[ "$TAG" != null ] || continue
echo "-- Package $PACKAGE"
# TODO(crueter): Support for forgejo_token?
endpoint="/repos/$REPO/tags"
if command -v gh >/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
-134
View File
@@ -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
}
-28
View File
@@ -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
-228
View File
@@ -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 <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
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)"
-11
View File
@@ -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"
-7
View File
@@ -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
-27
View File
@@ -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
-158
View File
@@ -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
-62
View File
@@ -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 <<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"
[ -n "$PACKAGE" ] || usage
[ -n "$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 [ -n "$VERSION_PREFIX" ]; then
NEW_VERSION=$(echo "$NEW_VERSION" | sed "s/$VERSION_PREFIX//g")
fi
fi
if [ "$SHA" != null ]; then
JSON=$(echo "$JSON" | jq ".sha = \"$NEW_VERSION\"")
elif [ "$CI" = "true" ] || [ "$HAS_REPLACE" = "true" ]; then
JSON=$(echo "$JSON" | jq ".version = \"$NEW_VERSION\"")
else
JSON=$(echo "$JSON" | jq ".tag = \"$NEW_VERSION\"")
fi
echo "-- * Updating $PACKAGE to version $NEW_VERSION"
# TODO: ci hash thing please
if [ "$CI" != true ]; then
echo "-- * -- Updating hash"
# shellcheck disable=SC1091
. "$SCRIPTS"/vars.sh
HASH=$("$SCRIPTS"/util/url-hash.sh "$DOWNLOAD")
JSON=$(echo "$JSON" | jq ".hash = \"$HASH\"")
fi
"$SCRIPTS"/util/replace.sh "$PACKAGE" "$JSON"
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2026 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
# check if a package is defined
echo "$LIBS" | grep "$1" >/dev/null 2>&1