1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
#!/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 "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"
[ -n "$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 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\"}"
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
[ -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
# 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\"}"
[ -z "$TAGNAME" ] || jq_input="$jq_input + {tag: \"$TAGNAME\"}"
[ -z "$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)"
|