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 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
|
#!/bin/bash
# stop on any unhandled error
set -e
# @TODO: pipefail not in POSIX
set -o pipefail
# show commands (debug)
#set -x
display_help() {
cat << EOF
usage: debcraft <build|improve|test|release|update|shell|logs|prune> [options] [<path|pkg|srcpkg|dsc|git-url>]
Debcraft is a tool to easily build .deb packages. The 'build' argument accepts
as a subargument any of:
* path to directory with program sources including a debian/ subdirectory with
* the Debian packaging instructions
* path to a .dsc file and source tarballs that can be built into a .deb
* Debian package name, or source package name, that apt can download
* git http(s) or ssh URL that can be downloaded and built
The command 'improve' will try to apply various improvements to the package
based on tools in Debian that automate package maintenance. The command 'test'
will run the Debian-specific regression test suite if the package has
autopkgtest support, and drop to a shell for investigation if tests failed to
pass. The command 'release' is intended to be used to upload a package that is
ready to be released and command 'update' will try to update the package to
laters upstream version is package git repository layout is compatible.
The command 'shell' can be used to play around in the container and 'prune' will
clean up temporary files by Debcraft.
In addition to parameters below, anything passed in DEB_BUILD_OPTIONS will also
be honored (currently DEB_BUILD_OPTIONS='$DEB_BUILD_OPTIONS'). Successful builds
include running './debian/rules clean' to clean up artifacts, while failed
builds will leave them around for inspection.
Note that Debcraft builds never runs as root, and thus packages with
DEB_RULES_REQUIRES_ROOT are not supported.
optional arguments:
--build-dirs-path Path for writing build files and artifacts (default: ~/.cache/debcraft)
--distribution Linux distribution to build in (default: debian:sid)
--container-command container command to use (default: podman)
--host-architecture host architecture to use when performing a cross build
--skip-sources build only binaries and skip creating a source
tarball to make the build slightly faster
('debcraft build' only)
--with-binaries create a release with both source and binaries,
for example with intent to upload to NEW
('debcraft release' only)
--pull ensure container base is updated
--copy perform the build on a copy of the package directory
--clean ensure sources are clean before and after build
(only needed on packages with incomplete 'debian/clean'
or 'debian/.gitignore' definitions)
--debug emit debug information
-h, --help display this help and exit
--version display version and exit
To learn more, or to contribute to Debcraft, see project page at
https://salsa.debian.org/debian/debcraft
To gain more Debian Developer knowledge, please read
https://www.debian.org/doc/manuals/developers-reference/
and https://www.debian.org/doc/debian-policy/
EOF
}
# Canonicalize script name if was run via symlink
DEBCRAFT_CMD_PATH="$(readlink --canonicalize-existing --verbose "$0")"
DEBCRAFT_LIB_DIR="$(dirname "$DEBCRAFT_CMD_PATH")/src"
# Assume system installation directory if not running from source directory
if [ ! -r "$DEBCRAFT_LIB_DIR/container/output.inc.sh" ]
then
DEBCRAFT_LIB_DIR="/usr/share/debcraft"
fi
# If Debcraft itself was run in a git repository, include the git commit id
display_version() {
if [ -e "$(dirname "$DEBCRAFT_CMD_PATH")/.git" ]
then
cd "$(dirname "$DEBCRAFT_CMD_PATH")"
if [ -z "$(git tag --list)" ]
then
echo "DEBCRAFT ERROR: Unable to view latest git tag. Please run 'git fetch --tags'."
exit 1
fi
LATEST_TAG="$(git describe --first-parent --abbrev=0)"
LATEST_VERSION="$(echo "$LATEST_TAG" | grep --only-matching --basic-regexp '[0-9.]*')"
LATEST_COMMIT="$(git rev-parse --short HEAD)"
echo "Debcraft $LATEST_VERSION-$LATEST_COMMIT"
elif [ -f /usr/share/doc/debcraft/changelog.gz ]
then
VERSION="$(
zgrep --only-matching --max-count=1 --perl-regexp '\(\K[^\)]*' \
/usr/share/doc/debcraft/changelog.gz
)"
echo "Debcraft $VERSION"
else
echo "Debcraft version unknown: neither git version control nor installed package was found"
fi
}
# Debug flag detection must run before output.inc.sh is loaded
case "$@" in
*--debug*)
export DEBUG="true"
;;
esac
# Output formatting library is reused inside container as well
# shellcheck source=src/container/output.inc.sh
source "$DEBCRAFT_LIB_DIR/container/output.inc.sh"
# shellcheck source=src/distributions.inc.sh
source "$DEBCRAFT_LIB_DIR/distributions.inc.sh"
# shellcheck source=src/generic.inc.sh
source "$DEBCRAFT_LIB_DIR/generic.inc.sh"
if [ -z "$1" ]
then
log_error "Missing argument <build|improve|test|release|shell|logs|prune>"
echo
display_help
exit 1
fi
while :
log_debug "Parse option/argument: $1"
do
case "$1" in
--build-dirs-path)
if [ -z "$2" ] || [ ! -d "$2" ]
then
log_error "Parameter --build-dirs-path requires a path"
exit 1
fi
BUILD_DIRS_PATH="$(readlink -f "$2")"
export BUILD_DIRS_PATH
log_debug "Using BUILD_DIRS_PATH=$BUILD_DIRS_PATH"
shift 2
;;
--distribution)
# The value is the next argument, e.g. '--distribution bookworm'
export DISTRIBUTION="$2"
log_debug "Using DISTRIBUTION=$2"
shift 2
;;
--container-command)
export CONTAINER_CMD="$2"
log_debug "Using CONTAINER_CMD=$2"
shift 2
;;
--host-architecture)
export HOST_ARCH="$2"
shift 2
;;
--skip-sources)
export SKIP_SOURCES="true"
shift
;;
--with-binaries)
export WITH_BINARIES="true"
shift
;;
--pull)
export PULL="true"
shift
;;
--clean)
export CLEAN="true"
shift
;;
--copy)
export COPY="true"
shift
;;
--debug)
# Debug mode detection is already done earlier, ignore it at this stage
shift
;;
-h | --help)
display_help # Call your function
exit 0
;;
--version)
display_version
exit 0
;;
--)
# No more options
shift
break
;;
-*)
log_error "Unknown option: $1"
## or call function display_help
exit 1
;;
build | improve | test | release | update | shell | logs | prune)
export ACTION="$1"
shift
;;
*)
export TARGET="$1"
# No more options or arguments
break
;;
esac
done
if [ -z "$ACTION" ]
then
# If ACTION is empty the TARGET might have been populated
log_error "Argument '$TARGET' not one of <build|improve|test|release|update|shell|logs|prune>"
echo
display_help
exit 1
fi
if [ -n "$SKIP_SOURCES" ] && [ "$ACTION" != "build" ]
then
log_error "Parameter --skip-sources can only be used with action 'build'"
echo
display_help
exit 1
fi
if [ -n "$WITH_BINARIES" ] && [ "$ACTION" != "release" ]
then
log_error "Parameter --with-binaries can only be used with action 'release'"
echo
display_help
exit 1
fi
if [ -n "$DISTRIBUTION" ] && { [ "$ACTION" == "logs" ] || [ "$ACTION" == "update" ]; }
then
log_error "Parameter --distribution is not supported for action '$ACTION'"
echo
display_help
exit 1
fi
log_debug_var ACTION
# Configure general program behaviour after user options and arguments have been parsed
# shellcheck source=src/config-general.inc.sh
source "$DEBCRAFT_LIB_DIR/config-general.inc.sh"
# @TODO: Nag of dependencies are not available: git, dpkg-parsechangelog, rsync,
# notify-send, paplay, tee, sed
#
# Bash must be new enough to have 'mapfile'.
# Docker does not support '--noheading', so the command will always output at
# least one line
# Hack: Listing images is very slow on Podman, so branch off two different tests
# shellcheck disable=2235 # two subshells necessary in this case
if ([ "$CONTAINER_TYPE" == 'podman' ] && \
! grep --quiet debcraft ~/.local/share/containers/storage/overlay-images/images.json 2>/dev/null) \
|| \
([ "$CONTAINER_TYPE" == 'docker' ] && \
[ "$($CONTAINER_CMD images 'debcraft*' | wc -l)" -eq 1 ])
then
log_warn "No previous Debcraft container was found and thus the first run of"
log_warn "this tool is expected to be slow as the container base layer needs"
log_warn "to be built. Re-runs of Debcraft will be fast."
fi
log_debug_var TARGET
if [ -z "$TARGET" ]
then
# If no argument defined, default to current directory
TARGET="$(pwd)"
elif [ -d "$TARGET" ]
then
# If an argument was given, and it is a directory, use TARGET as-is
:
elif [ ! -d "$TARGET" ] && [[ "build improve update" =~ $ACTION ]]
then
# If the argument exists, but didn't point to a valid path, try to use the
# argument to download the package
# shellcheck source=src/downloader-container.inc.sh
source "$DEBCRAFT_LIB_DIR/downloader-container.inc.sh"
# shellcheck source=src/downloader.inc.sh
source "$DEBCRAFT_LIB_DIR/downloader.inc.sh"
# After a download, a subdirectory <TARGET>-<VERSION> might be present, and
# if a container build already ran, also a debcraft-container-<TARGET> will
# be present and that should be excluded from the listing below that aims to
# find the downloaded sources.
# shellcheck disable=SC2012
NEWEST_DIRECTORY="$(ls --sort=time --time=ctime --format=single-column --group-directories-first --hide="debcraft-container-*" | head --lines=1 || true)"
# Note! Use ctime above as dpkg-source will keep original mtime for packages
# and ensure it never emits exit codes, only text output
# Remove shortest pattern from end of variable, e.g. 'xz-utils-5.2.4' -> 'xz-utils'
# (https://tldp.org/LDP/abs/html/parameter-substitution.html)
#PACKAGE="${NEWEST_DIRECTORY%-*}"
# Ensure source directory exists with the package name, e.g. 'grep' -> 'grep-3.4'
#ln --verbose --force --symbolic "$NEWEST_DIRECTORY" "$PACKAGE"
# Newest directory contains the downloaded source package now, use it as TARGET
export TARGET="$NEWEST_DIRECTORY"
else
log_error "Debcraft command '$ACTION' can only used after a build has run," \
"and a directory with the Debian package source exist."
exit 1
fi
log_debug_var TARGET
# The previous step guarantees that the source directory either exits, was
# downloaded or the script execution stopped. From here onwards the script can
# assume that $PWD is a working directory with sources.
cd "$TARGET" || (log_error "Unable to change directory to $TARGET"; exit 1)
log_debug_var PWD
if [ -f "debian/changelog" ]
then
# If parsing the changelog emits exit code, intentionally stop here
PACKAGE="$(head -n 1 debian/changelog | cut -d ' ' -f 1)"
else
log_error "Directory '$TARGET' is not a valid source package directory as" \
"debian/changelog was not found"
exit 1
fi
log_info "Running in path $PWD that has Debian package sources for '$PACKAGE'"
# Make sure sources are clean on actions that depend on it
if [ "$ACTION" == "build" ] || [ "$ACTION" == "release" ]
then
# Every deb build potentially generates these temporary files, and it is safe
# to assume that they should be deleted before restarting the build, so do it
# automatically to make the overall experience friendlier
rm -rfv debian/.debhelper debian/debhelper-build-stamp \
debian/debhelper.log debian/*.debhelper.log \
debian/substvars debian/*.substvars debian/files
# If there are still more files, the user needs to make a decision
if [ -z "$CLEAN" ] &&
[ -z "$COPY" ] &&
[ -d "$PWD/.git" ] &&
[ -n "$(git status --porcelain --ignored --untracked-files=all)" ]
then
log_error "Modified or additional files found"\
"\n$(git status --porcelain --ignored --untracked-files=all | head)"\
"\nThese may be leftovers of a failed build, or of an incomplete"\
"\nrun of './debian/rules clean' after a successful build." \
"\nUse --clean to remove all untracked and uncommitted files"\
"\nor run with --copy to build in a separate new directory."
exit 1
fi
fi
# @TODO: If repository is a git clone, but not a `gbp clone`, it can be converted
# to gbp with `gbp pull --verbose --ignore-branch --pristine-tar --track-missing`
# Clean up before the build if applicable
reset_if_source_repository_and_option_clean
# Configure program behaviour after user options and arguments have been parsed
# shellcheck source=src/config-package.inc.sh
source "$DEBCRAFT_LIB_DIR/config-package.inc.sh"
# If the action needs to run in a container, automatically create it
if [ "$ACTION" != "update" ] && [ "$ACTION" != "prune" ] && [ "$ACTION" != "logs" ]
then
# shellcheck source=src/container.inc.sh
source "$DEBCRAFT_LIB_DIR/container.inc.sh"
fi
case "$ACTION" in
build)
# shellcheck source=src/build.inc.sh
source "$DEBCRAFT_LIB_DIR/build.inc.sh"
;;
improve)
# shellcheck source=src/improve.inc.sh
source "$DEBCRAFT_LIB_DIR/improve.inc.sh"
;;
test)
# shellcheck source=src/test.inc.sh
source "$DEBCRAFT_LIB_DIR/test.inc.sh"
;;
release)
# shellcheck source=src/release.inc.sh
source "$DEBCRAFT_LIB_DIR/release.inc.sh"
# shellcheck source=src/release-ppa.inc.sh
source "$DEBCRAFT_LIB_DIR/release-ppa.inc.sh"
# shellcheck source=src/release-dput.inc.sh
source "$DEBCRAFT_LIB_DIR/release-dput.inc.sh"
;;
update)
# shellcheck source=src/update.inc.sh
source "$DEBCRAFT_LIB_DIR/update.inc.sh"
;;
shell)
# shellcheck source=src/shell.inc.sh
source "$DEBCRAFT_LIB_DIR/shell.inc.sh"
;;
logs)
# shellcheck source=src/logs.inc.sh
source "$DEBCRAFT_LIB_DIR/logs.inc.sh"
;;
prune)
# shellcheck source=src/prune.inc.sh
source "$DEBCRAFT_LIB_DIR/prune.inc.sh"
;;
esac
# Clean up after a successful build if applicable
reset_if_source_repository_and_option_clean
|