diff options
author | Andreas Baumann <mail@andreasbaumann.cc> | 2023-05-29 16:05:59 +0200 |
---|---|---|
committer | Andreas Baumann <mail@andreasbaumann.cc> | 2023-05-29 16:05:59 +0200 |
commit | be8ac63f28a402acbf7e21972ac5d3b45bed1f79 (patch) | |
tree | 6eb83c09f6549ae41988a25243a2b424a4f99a4e /src | |
parent | bfc22eea7e4c6877fe8b9d89fa574cb0729466db (diff) | |
parent | a07df0beeaeea1bf5665512bacc7a013eece4602 (diff) | |
download | devtools-be8ac63f28a402acbf7e21972ac5d3b45bed1f79.tar.xz |
merged with devtools 1.0.1 upstream (git repo migration)
Diffstat (limited to 'src')
43 files changed, 3096 insertions, 362 deletions
diff --git a/src/arch-nspawn.in b/src/arch-nspawn.in index ae50043..11af616 100644 --- a/src/arch-nspawn.in +++ b/src/arch-nspawn.in @@ -2,8 +2,12 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) -m4_include(lib/archroot.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/archroot.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh + # umask might have been changed in /etc/profile # ensure that sane default is set again @@ -28,6 +32,9 @@ usage() { exit 1 } +# save all args for check_root +orig_args=("$@") + while getopts 'hC:M:c:f:s' arg; do case "$arg" in C) pac_conf="$OPTARG" ;; @@ -42,7 +49,7 @@ done shift $((OPTIND - 1)) (( $# < 1 )) && die 'You must specify a directory.' -check_root +check_root "" "${BASH_SOURCE[0]}" "${orig_args[@]}" working_dir=$(readlink -f "$1") shift 1 @@ -54,7 +61,7 @@ if (( ${#cache_dirs[@]} == 0 )); then fi # shellcheck disable=2016 -host_mirrors=($(pacman-conf --config "${pac_conf:-$working_dir/etc/pacman.conf}" --repo extra Server 2> /dev/null | sed -r 's#(.*/)extra/os/.*#\1$repo/os/$arch#;t;s#(.*/)[^/]+/extra(/?)$#\1$arch/$repo\2#')) +host_mirrors=($(pacman-conf --repo extra Server 2> /dev/null | sed -r 's#(.*/)extra/os/.*#\1$repo/os/$arch#')) for host_mirror in "${host_mirrors[@]}"; do if [[ $host_mirror == *file://* ]]; then diff --git a/src/archbuild.in b/src/archbuild.in index a5eb9a0..d023e0d 100644 --- a/src/archbuild.in +++ b/src/archbuild.in @@ -2,8 +2,12 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) -m4_include(lib/archroot.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/archroot.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh + base_packages=(base-devel) makechrootpkg_args=(-c -n -C) @@ -23,16 +27,32 @@ if [[ -f "@pkgdatadir@/setarch-aliases.d/${arch}" ]]; then else set_arch="${arch}" fi + +# Architecture-specific Mount +arch_mounts=() +if [[ -f "@pkgdatadir@/mount.d/${arch}" ]]; then + mapfile -t arch_mounts < "@pkgdatadir@/mount.d/${arch}" +fi +for arch_mount in "${arch_mounts[@]}"; do + if [[ $arch_mount = rw* ]]; then + arch_mount=${arch_mount#rw } + in_array "$arch_mount" "${makechrootpkg_args[@]}" || makechrootpkg_args+=("-d" "$arch_mount") + elif [[ $arch_mount = ro* ]]; then + arch_mount=${arch_mount#ro } + in_array "$arch_mount" "${makechrootpkg_args[@]}" || makechrootpkg_args+=("-D" "$arch_mount") + fi +done + chroots='/var/lib/archbuild' clean_first=false -pacman_config="@pkgdatadir@/pacman-${repo}.conf" -if [[ -f @pkgdatadir@/pacman-${repo}-${arch}.conf ]]; then - pacman_config="@pkgdatadir@/pacman-${repo}-${arch}.conf" +pacman_config="@pkgdatadir@/pacman.conf.d/${repo}.conf" +if [[ -f @pkgdatadir@/pacman.conf.d/${repo}-${arch}.conf ]]; then + pacman_config="@pkgdatadir@/pacman.conf.d/${repo}-${arch}.conf" fi -makepkg_config="@pkgdatadir@/makepkg-${arch}.conf" -if [[ -f @pkgdatadir@/makepkg-${repo}-${arch}.conf ]]; then - makepkg_config="@pkgdatadir@/makepkg-${repo}-${arch}.conf" +makepkg_config="@pkgdatadir@/makepkg.conf.d/${arch}.conf" +if [[ -f @pkgdatadir@/makepkg.conf.d/${repo}-${arch}.conf ]]; then + makepkg_config="@pkgdatadir@/makepkg.conf.d/${repo}-${arch}.conf" fi usage() { @@ -54,7 +74,7 @@ while getopts 'hcr:' arg; do esac done -check_root SOURCE_DATE_EPOCH,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,MAKEFLAGS,PACKAGER,GNUPGHOME,PKGEXT +check_root SOURCE_DATE_EPOCH,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,MAKEFLAGS,PACKAGER,GNUPGHOME "${BASH_SOURCE[0]}" "$@" if [ "${arch}" = "x86_64" ]; then cache_dir='' diff --git a/src/archco.in b/src/archco.in deleted file mode 100644 index a93d819..0000000 --- a/src/archco.in +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# -# SPDX-License-Identifier: GPL-3.0-or-later - -m4_include(lib/common.sh) - -scriptname=${0##*/} - -if [[ -z $1 ]]; then - printf 'Usage: %s <package name>...\n' "$scriptname" - exit 1 -fi - -case $scriptname in - archco) - SVNURL="svn+ssh://svn-packages@repos.archlinux.org/srv/repos/svn-packages/svn";; - communityco) - SVNURL="svn+ssh://svn-community@repos.archlinux.org/srv/repos/svn-community/svn";; - *) - die "Couldn't find svn url for %s" "$scriptname" - ;; -esac - -for i in "$@"; do - svn co "$SVNURL/$i" -done diff --git a/src/archrelease.in b/src/archrelease.in index 3490ee2..818b0ca 100644 --- a/src/archrelease.in +++ b/src/archrelease.in @@ -2,8 +2,19 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) -m4_include(lib/valid-tags.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/valid-tags.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-tags.sh + +set -e + + +# Deprecation warning +if [[ -z $_DEVTOOLS_COMMAND ]]; then + warning "${0##*/} is deprecated and will be removed. Use 'pkgctl release' instead" +fi # parse command line options FORCE= @@ -34,54 +45,50 @@ if [[ ! -f PKGBUILD ]]; then die 'archrelease: PKGBUILD not found' fi -trunk=${PWD##*/} +# shellcheck source=contrib/makepkg/PKGBUILD.proto +. ./PKGBUILD +pkgbase=${pkgbase:-$pkgname} +pkgver=$(get_full_version "$pkgbase") +gittag=$(get_tag_from_pkgver "$pkgver") -# Normally this should be trunk, but it may be something -# such as 'gnome-unstable' -IFS='/' read -r -d '' -a parts <<< "$PWD" -if [[ "${parts[*]:(-2):1}" == "repos" ]]; then - die 'archrelease: Should not be in repos dir (try from trunk/)' +# Check if releasing from a branch +if ! branchname=$(git symbolic-ref --short HEAD); then + die 'not on any branch' fi -unset parts - -if [[ $(svn status -q) ]]; then - die 'archrelease: You have not committed your changes yet!' +if [[ "${branchname}" != main ]]; then + die 'must be run from the main branch' fi -pushd .. >/dev/null -mapfile -t known_files < <(svn ls -r HEAD "$trunk") -wait $! || die "failed to discover committed files" - -# gracefully handle files containing an "@" character -known_files=("${known_files[@]/%/@}") - -# update repo directory first to avoid a commit failure -svn up repos +# Check if remote origin is setup properly +if ! giturl=$(git remote get-url origin) || [[ ${giturl} != *${GIT_PACKAGING_URL_SSH}* ]]; then + die "remote origin is not configured, run 'pkgctl repo configure'" +fi +if ! git ls-remote origin >/dev/null; then + die "configured remote origin may not exist, run 'pkgctl repo create ${pkgbase}' to create it" +fi -for tag in "$@"; do - stat_busy "Copying %s to %s" "${trunk}" "${tag}" +msg 'Fetching remote changes' +git fetch --prune --prune-tags origin || die 'failed to fetch remote changes' - if [[ -d repos/$tag ]]; then - mapfile -t trash < <(svn ls --recursive "repos/$tag") - wait $! || die "failed to discover existing files" - if (( ${#trash[@]} )); then - trash=("${trash[@]/#/repos/$tag/}") - svn rm -q "${trash[@]/%/@}" - fi - else - mkdir -p "repos/$tag" - svn add --parents -q "repos/$tag" +# Check if local branch is up to date and contains the latest origin commit +if remoteref=$(git rev-parse "origin/${branchname}" 2>/dev/null); then + if [[ $(git branch "${branchname}" --contains "${remoteref}" --format '%(refname:short)') != "${branchname}" ]]; then + die "local branch is out of date, run 'git pull --rebase'" fi +fi - # copy all files at once from trunk to the subdirectory in repos/ - svn copy -q -r HEAD "${known_files[@]/#/$trunk/}" "repos/$tag/" - - stat_done -done - -stat_busy "Releasing package" -printf -v tag_list ", %s" "$@"; tag_list="${tag_list#, }" -svn commit -q -m "archrelease: copy ${trunk} to $tag_list" || abort -stat_done +# If the tag exists we check if it's properly signed and that it +# matches the working directory PKGBUILD. +if git tag --verify "$gittag" &> /dev/null; then + cwd_checksum=$(sha256sum PKGBUILD|cut -d' ' -f1) + tag_checksum=$(git show "${gittag}:PKGBUILD" | sha256sum |cut -d' ' -f1) + if [[ "$cwd_checksum" != "$tag_checksum" ]]; then + die "tagged PKGBUILD is not the same as the working dir PKGBUILD" + fi + git push --tags --set-upstream origin main || abort + exit 0 +fi -popd >/dev/null +msg "Releasing package" +git tag --sign --message="Package release ${pkgver}" "$gittag" || abort +git push --tags --set-upstream origin main || abort diff --git a/src/checkpkg.in b/src/checkpkg.in index fe5b71a..ccb7259 100644 --- a/src/checkpkg.in +++ b/src/checkpkg.in @@ -2,7 +2,10 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + usage() { cat <<- _EOF_ diff --git a/src/commitpkg.in b/src/commitpkg.in index 235d12b..8a8087a 100644 --- a/src/commitpkg.in +++ b/src/commitpkg.in @@ -2,7 +2,39 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + +source /usr/share/makepkg/util/util.sh + + +check_pkgbuild_validity() { + # shellcheck source=contrib/makepkg/PKGBUILD.proto + . ./PKGBUILD + + # skip when there are no sources available + if (( ! ${#source[@]} )); then + return + fi + + # validate sources hash algo is at least > sha1 + local bad_algos=("cksums" "md5sums" "sha1sums") + local good_hash_algo=false + + # from makepkg libmakepkg/util/schema.sh + for integ in "${known_hash_algos[@]}"; do + local sumname="${integ}sums" + if [[ -n ${!sumname} ]] && ! in_array "${sumname}" "${bad_algos[@]}"; then + good_hash_algo=true + break + fi + done + + if ! $good_hash_algo; then + die "PKGBUILD lacks a secure cryptographic checksum, insecure algorithms: ${bad_algos[*]}" + fi +} # Source makepkg.conf; fail if it is not found if [[ -r '/etc/makepkg.conf' ]]; then @@ -23,10 +55,25 @@ fi cmd=${0##*/} +# Deprecation warning +if [[ -z $_DEVTOOLS_COMMAND ]]; then + warning "${cmd} is deprecated and will be removed. Use 'pkgctl release' instead" +fi + if [[ ! -f PKGBUILD ]]; then die 'No PKGBUILD file' fi +if ! repo_spec=$(git config --local devtools.version) || [[ ${repo_spec} != "${GIT_REPO_SPEC_VERSION}" ]]; then + error "repository specs are out of date, try:" + msg2 'pkgctl repo configure' + exit 1 +fi + +if [[ "$(git symbolic-ref --short HEAD)" != main ]]; then + die 'must be run from the main branch' +fi + source=() # shellcheck source=contrib/makepkg/PKGBUILD.proto . ./PKGBUILD @@ -60,7 +107,7 @@ if (( ${#validpgpkeys[@]} != 0 )); then export-pkgbuild-keys || die 'Failed to export valid PGP keys for source files' fi - svn add --parents --force keys/pgp/* + git add --force -- keys/pgp/* fi # find files which should be under source control @@ -79,15 +126,21 @@ for key in "${validpgpkeys[@]}"; do needsversioning+=("keys/pgp/$key.asc") done -# assert that they really are controlled by SVN +# assert that they really are controlled by git if (( ${#needsversioning[*]} )); then - # svn status's output is only two columns when the status is unknown - while read -r status filename; do - [[ $status = '?' ]] && unversioned+=("$filename") - done < <(svn status -v "${needsversioning[@]}") - (( ${#unversioned[*]} )) && die "%s is not under version control" "${unversioned[@]}" + for file in "${needsversioning[@]}"; do + # skip none existing files + if [[ ! -f "${file}" ]]; then + continue + fi + if ! git ls-files --error-unmatch "$file"; then + die "%s is not under version control" "$file" + fi + done fi + +server=${PACKAGING_REPO_RELEASE_HOST} rsyncopts=(-e ssh -p '--chmod=ug=rw,o=r' -c -h -L --progress --partial -y) archreleaseopts=() while getopts ':l:a:s:f' flag; do @@ -121,31 +174,50 @@ for _arch in "${arch[@]}"; do fi done -if [[ -z $server ]]; then - server='repos.archlinux.org' +# check for PKGBUILD standards +check_pkgbuild_validity + +# auto generate .SRCINFO if present +if [[ -f .SRCINFO ]]; then + stat_busy 'Generating .SRCINFO' + makepkg --printsrcinfo > .SRCINFO + git add .SRCINFO + stat_done fi -if [[ -n $(svn status -q) ]]; then - msgtemplate="upgpkg: $pkgbase $(get_full_version)" +if [[ -n $(git status --porcelain --untracked-files=no) ]]; then + stat_busy 'Staging files' + for f in $(git ls-files --modified); do + git add "$f" + done + for f in $(git ls-files --deleted); do + git rm "$f" + done + stat_done + + msgtemplate="upgpkg: $(get_full_version)" if [[ -n $1 ]]; then - stat_busy 'Committing changes to trunk' - svn commit -q -m "${msgtemplate}: ${1}" || die + stat_busy 'Committing changes' + git commit -q -m "${msgtemplate}: ${1}" || die stat_done else - msgfile="$(mktemp)" + [[ -z ${WORKDIR:-} ]] && setup_workdir + msgfile=$(mktemp --tmpdir="${WORKDIR}" commitpkg.XXXXXXXXXX) echo "$msgtemplate" > "$msgfile" - if [[ -n $SVN_EDITOR ]]; then - $SVN_EDITOR "$msgfile" + if [[ -n $GIT_EDITOR ]]; then + $GIT_EDITOR "$msgfile" || die elif [[ -n $VISUAL ]]; then - $VISUAL "$msgfile" + $VISUAL "$msgfile" || die elif [[ -n $EDITOR ]]; then - $EDITOR "$msgfile" + $EDITOR "$msgfile" || die + elif giteditor=$(git config --get core.editor); then + $giteditor "$msgfile" || die else - vi "$msgfile" + die "No usable editor found (tried \$GIT_EDITOR, \$VISUAL, \$EDITOR, git config [core.editor])." fi [[ -s $msgfile ]] || die - stat_busy 'Committing changes to trunk' - svn commit -q -F "$msgfile" || die + stat_busy 'Committing changes' + git commit -v -q -F "$msgfile" || die unlink "$msgfile" stat_done fi @@ -219,23 +291,3 @@ if [[ ${#uploads[*]} -gt 0 ]]; then msg 'Uploading all package and signature files' rsync "${rsyncopts[@]}" "${uploads[@]}" "$server:staging/$repo/" || die fi - -if [[ "${arch[*]}" == 'any' ]]; then - if [[ -d ../repos/$repo-x86_64 ]]; then - pushd ../repos/ >/dev/null - stat_busy "Removing %s" "$repo-x86_64" - svn rm -q "$repo-x86_64" - svn commit -q -m "Removed $repo-x86_64 for $pkgname" - stat_done - popd >/dev/null - fi -else - if [[ -d ../repos/$repo-any ]]; then - pushd ../repos/ >/dev/null - stat_busy "Removing %s" "$repo-any" - svn rm -q "$repo-any" - svn commit -q -m "Removed $repo-any for $pkgname" - stat_done - popd >/dev/null - fi -fi diff --git a/src/crossrepomove.in b/src/crossrepomove.in deleted file mode 100644 index 08a3067..0000000 --- a/src/crossrepomove.in +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash -# -# SPDX-License-Identifier: GPL-3.0-or-later - -m4_include(lib/common.sh) - -scriptname=${0##*/} - -if [[ -z $1 ]]; then - printf 'Usage: %s [pkgbase]\n' "$scriptname" - exit 1 -fi - -pkgbase="${1}" - -case $scriptname in - extra2community) - source_name='packages' - target_name='community' - source_repo='extra' - target_repo='community' - ;; - community2extra) - source_name='community' - target_name='packages' - source_repo='community' - target_repo='extra' - ;; - *) - die "Couldn't find configuration for %s" "$scriptname" - ;; -esac - -server='repos.archlinux.org' -source_svn="svn+ssh://svn-${source_name}@${server}/srv/repos/svn-${source_name}/svn" -target_svn="svn+ssh://svn-${target_name}@${server}/srv/repos/svn-${target_name}/svn" -source_dbscripts="/srv/repos/svn-${source_name}/dbscripts" -target_dbscripts="/srv/repos/svn-${target_name}/dbscripts" - -setup_workdir - -pushd "$WORKDIR" >/dev/null - -msg "Downloading sources for %s" "${pkgbase}" -svn -q checkout -N "${target_svn}" target_checkout -mkdir -p "target_checkout/${pkgbase}/repos" -svn -q export "${source_svn}/${pkgbase}/trunk" "target_checkout/${pkgbase}/trunk" || die -# shellcheck source=contrib/makepkg/PKGBUILD.proto -. "target_checkout/${pkgbase}/trunk/PKGBUILD" - -msg "Downloading packages for %s" "${pkgbase}" -for _arch in "${arch[@]}"; do - if [[ "${_arch[*]}" == 'any' ]]; then - repo_arch='x86_64' - else - repo_arch=${_arch} - fi - for _pkgname in "${pkgname[@]}"; do - fullver=$(get_full_version "$_pkgname") - pkgpath="/srv/ftp/${source_repo}/os/${repo_arch}/${_pkgname}-${fullver}-${_arch}.pkg.tar.*" - debugpath="/srv/ftp/${source_repo}-debug/os/${repo_arch}/${_pkgname}-debug-${fullver}-${_arch}.pkg.tar.*" - # Fail if $pkgpath doesn't match but keep $debugpath optional - # shellcheck disable=2029 - ssh "${server}" "bash -c ' - install ${pkgpath} -Dt staging/${target_repo} && - (install ${debugpath} -Dt staging/${target_repo} 2>/dev/null || true) - '" || die - done -done - -msg "Adding %s to %s" "${pkgbase}" "${target_repo}" -svn -q add "target_checkout/${pkgbase}" -svn -q commit -m"${scriptname}: Moving ${pkgbase} from ${source_repo} to ${target_repo}" target_checkout -pushd "target_checkout/${pkgbase}/trunk" >/dev/null -archrelease "${arch[@]/#/$target_repo-}" || die -popd >/dev/null - -# shellcheck disable=2029 -ssh "${server}" "${target_dbscripts}/db-update" || die - -msg "Removing %s from %s" "${pkgbase}" "${source_repo}" -for _arch in "${arch[@]}"; do - # shellcheck disable=2029 - ssh "${server}" "${source_dbscripts}/db-remove ${source_repo} ${_arch} ${pkgbase}" -done -svn -q checkout -N "${source_svn}" source_checkout -svn -q up "source_checkout/${pkgbase}" -svn -q rm "source_checkout/${pkgbase}" -svn -q commit -m"${scriptname}: Moving ${pkgbase} from ${source_repo} to ${target_repo}" source_checkout - -popd >/dev/null diff --git a/src/diffpkg.in b/src/diffpkg.in index 10b0904..160c4ba 100644 --- a/src/diffpkg.in +++ b/src/diffpkg.in @@ -2,11 +2,15 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + usage() { - cat <<- _EOF_ - Usage: ${BASH_SOURCE[0]##*/} [OPTIONS] [MODES] [FILE|PKGNAME...] + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [MODES] [FILE|PKGNAME...] Searches for a locally built package corresponding to the PKGBUILD, and downloads the last version of that package from the Pacman repositories. @@ -17,8 +21,8 @@ usage() { When given two packages, diff both packages against each other. In either case, a package name will be converted to a filename from the - cache, and diffpkg will proceed as though this filename was initially - specified. + cache, and '${COMMAND}' will proceed as though this filename was + initially specified. OPTIONS -M, --makepkg-config Set an alternate makepkg configuration file @@ -211,7 +215,8 @@ diff_pkgs() { [[ -f $oldpkg ]] || die "No such file: %s" "${oldpkg}" [[ -f $newpkg ]] || die "No such file: %s" "${newpkg}" - DIFFOPTIONS+=(--label "${oldpkg}" --label "${newpkg}") + local -a diffoptions + diffoptions=("${DIFFOPTIONS[@]}" --label "${oldpkg}" --label "${newpkg}") if (( TARLIST )); then tar_list "$oldpkg" > "$TMPDIR/old" @@ -232,7 +237,7 @@ diff_pkgs() { # Resolve dynamic auto width one we know the content to diff if [[ $DIFFWIDTH == --width=auto ]]; then AUTOLENGTH=$(file_diff_columns "$TMPDIR/old" "$TMPDIR/new") - DIFFOPTIONS+=("--width=${AUTOLENGTH}") + diffoptions+=("--width=${AUTOLENGTH}") fi # Print a header for side-by-side view as it lacks labels @@ -240,7 +245,7 @@ diff_pkgs() { printf -- "--- %s\n+++ %s\n" "${oldpkg}" "${newpkg}" fi - diff "${DIFFOPTIONS[@]}" "$TMPDIR/old" "$TMPDIR/new" + diff "${diffoptions[@]}" "$TMPDIR/old" "$TMPDIR/new" fi if (( DIFFOSCOPE )); then @@ -248,6 +253,7 @@ diff_pkgs() { fi } +shopt -s extglob fetch_pkg() { local pkg pkgdest pkgurl case $1 in @@ -295,10 +301,11 @@ fetch_pkg() { echo "$pkgdest" } +shopt -u extglob if (( $# < 2 )); then if [[ ! -f PKGBUILD ]]; then - die "This must be run in the directory of a built package.\nTry '$(basename "$0") --help' for more information." + die "This must be run in the directory of a built package.\nTry '${COMMAND} --help' for more information." fi # shellcheck source=contrib/makepkg/PKGBUILD.proto diff --git a/src/export-pkgbuild-keys.in b/src/export-pkgbuild-keys.in index 8697b3d..c19fc8b 100644 --- a/src/export-pkgbuild-keys.in +++ b/src/export-pkgbuild-keys.in @@ -2,7 +2,10 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + usage() { cat <<- _EOF_ @@ -35,7 +38,7 @@ if [[ ! -f PKGBUILD ]]; then fi mapfile -t validpgpkeys < <( - # shellcheck source=PKGBUILD.proto + # shellcheck source=contrib/makepkg/PKGBUILD.proto . ./PKGBUILD if (( ${#validpgpkeys[@]} )); then printf "%s\n" "${validpgpkeys[@]}" diff --git a/src/find-libdeps.in b/src/find-libdeps.in index 5a11580..afdf0f5 100644 --- a/src/find-libdeps.in +++ b/src/find-libdeps.in @@ -2,7 +2,10 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + set -e @@ -40,6 +43,7 @@ else pushd "$WORKDIR" >/dev/null fi +shopt -s extglob process_sofile() { # extract the library name: libfoo.so shopt -s extglob nullglob @@ -56,6 +60,7 @@ process_sofile() { soobjects+=("${soname}=${soversion}-${soarch}") fi } +shopt -u extglob case $script_mode in deps) find_args=(-perm -u+x);; diff --git a/src/finddeps.in b/src/finddeps.in index da7cb85..20d7095 100644 --- a/src/finddeps.in +++ b/src/finddeps.in @@ -4,7 +4,10 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + match=$1 diff --git a/src/lddd.in b/src/lddd.in index 12f8d67..80c22c4 100644 --- a/src/lddd.in +++ b/src/lddd.in @@ -4,7 +4,10 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + ifs=$IFS IFS="${IFS}:" diff --git a/src/lib/api/gitlab.sh b/src/lib/api/gitlab.sh new file mode 100644 index 0000000..e5f4237 --- /dev/null +++ b/src/lib/api/gitlab.sh @@ -0,0 +1,132 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_API_GITLAB_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_API_GITLAB_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/config.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/config.sh + +set -e + + +gitlab_api_call() { + local outfile=$1 + local request=$2 + local endpoint=$3 + local data=${4:-} + local error + + # empty token + if [[ -z "${GITLAB_TOKEN}" ]]; then + msg_error " api call failed: No token provided" + return 1 + fi + + if ! curl --request "${request}" \ + --url "https://${GITLAB_HOST}/api/v4/${endpoint}" \ + --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \ + --header "Content-Type: application/json" \ + --data "${data}" \ + --output "${outfile}" \ + --silent; then + msg_error " api call failed: $(cat "${outfile}")" + return 1 + fi + + # check for general purpose api error + if error=$(jq --raw-output --exit-status '.error' < "${outfile}"); then + msg_error " api call failed: ${error}" + return 1 + fi + + # check for api specific error messages + if ! jq --raw-output --exit-status '.id' < "${outfile}" >/dev/null; then + if jq --raw-output --exit-status '.message | keys[]' < "${outfile}" &>/dev/null; then + while read -r error; do + msg_error " api call failed: ${error}" + done < <(jq --raw-output --exit-status '.message|to_entries|map("\(.key) \(.value[])")[]' < "${outfile}") + elif error=$(jq --raw-output --exit-status '.message' < "${outfile}"); then + msg_error " api call failed: ${error}" + fi + return 1 + fi + + return 0 +} + +gitlab_api_get_user() { + local outfile username + + [[ -z ${WORKDIR:-} ]] && setup_workdir + outfile=$(mktemp --tmpdir="${WORKDIR}" pkgctl-gitlab-api.XXXXXXXXXX) + + # query user details + if ! gitlab_api_call "${outfile}" GET "user/"; then + msg_warn " Invalid token provided?" + exit 1 + fi + + # extract username from details + if ! username=$(jq --raw-output --exit-status '.username' < "${outfile}"); then + msg_error " failed to query username: $(cat "${outfile}")" + return 1 + fi + + printf "%s" "${username}" + return 0 +} + +# Convert arbitrary project names to GitLab valid path names. +# +# GitLab has several limitations on project and group names and also maintains +# a list of reserved keywords as documented on their docs. +# https://docs.gitlab.com/ee/user/reserved_names.html +# +# 1. replace single '+' between word boundaries with '-' +# 2. replace any other '+' with literal 'plus' +# 3. replace any special chars other than '_', '-' and '.' with '-' +# 4. replace consecutive '_-' chars with a single '-' +# 5. replace 'tree' with 'unix-tree' due to GitLab reserved keyword +gitlab_project_name_to_path() { + local name=$1 + printf "%s" "${name}" \ + | sed -E 's/([a-zA-Z0-9]+)\+([a-zA-Z]+)/\1-\2/g' \ + | sed -E 's/\+/plus/g' \ + | sed -E 's/[^a-zA-Z0-9_\-\.]/-/g' \ + | sed -E 's/[_\-]{2,}/-/g' \ + | sed -E 's/^tree$/unix-tree/g' +} + +gitlab_api_create_project() { + local pkgbase=$1 + local outfile data path project_path + + [[ -z ${WORKDIR:-} ]] && setup_workdir + outfile=$(mktemp --tmpdir="${WORKDIR}" pkgctl-gitlab-api.XXXXXXXXXX) + + project_path=$(gitlab_project_name_to_path "${pkgbase}") + + # create GitLab project + data='{ + "name": "'"${pkgbase}"'", + "path": "'"${project_path}"'", + "namespace_id": "'"${GIT_PACKAGING_NAMESPACE_ID}"'", + "request_access_enabled": "false" + }' + if ! gitlab_api_call "${outfile}" POST "projects/" "${data}"; then + return 1 + fi + + if ! path=$(jq --raw-output --exit-status '.path' < "${outfile}"); then + msg_error " failed to query path: $(cat "${outfile}")" + return 1 + fi + + printf "%s" "${path}" + return 0 +} diff --git a/src/lib/archroot.sh b/src/lib/archroot.sh new file mode 100644 index 0000000..8386c6c --- /dev/null +++ b/src/lib/archroot.sh @@ -0,0 +1,63 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later +: + +# shellcheck disable=2034 +CHROOT_VERSION='v5' + +## +# usage : check_root $keepenv +## +check_root() { + local keepenv=$1 + shift + local orig_argv=("$@") + + (( EUID == 0 )) && return + if type -P sudo >/dev/null; then + exec sudo --preserve-env="${keepenv}" -- "${orig_argv[@]}" + else + exec su root -c "$(printf ' %q' "${orig_argv[@]}")" + fi +} + +## +# usage : is_btrfs( $path ) +# return : whether $path is on a btrfs +## +is_btrfs() { + [[ -e "$1" && "$(stat -f -c %T "$1")" == btrfs ]] +} + +## +# usage : is_subvolume( $path ) +# return : whether $path is a the root of a btrfs subvolume (including +# the top-level subvolume). +## +is_subvolume() { + [[ -e "$1" && "$(stat -f -c %T "$1")" == btrfs && "$(stat -c %i "$1")" == 256 ]] +} + +## +# usage : subvolume_delete_recursive( $path ) +# +# Find all btrfs subvolumes under and including $path and delete them. +## +subvolume_delete_recursive() { + local subvol + + is_subvolume "$1" || return 0 + + while IFS= read -d $'\0' -r subvol; do + if ! subvolume_delete_recursive "$subvol"; then + return 1 + fi + done < <(find "$1" -mindepth 1 -xdev -depth -inum 256 -print0) + if ! btrfs subvolume delete "$1" &>/dev/null; then + error "Unable to delete subvolume %s" "$subvol" + return 1 + fi + + return 0 +} diff --git a/src/lib/auth.sh b/src/lib/auth.sh new file mode 100644 index 0000000..77d6a90 --- /dev/null +++ b/src/lib/auth.sh @@ -0,0 +1,72 @@ +#!/hint/bash +# +# This may be included with or without `set -euE` +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_AUTH_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_AUTH_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} + +set -e + + +pkgctl_auth_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [COMMAND] [OPTIONS] + + Authenticate with services like GitLab. + + COMMANDS + login Authenticate with the GitLab instance + status View authentication status + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} login --gen-access-token + $ ${COMMAND} status +_EOF_ +} + +pkgctl_auth() { + if (( $# < 1 )); then + pkgctl_auth_usage + exit 0 + fi + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_auth_usage + exit 0 + ;; + login) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/auth/login.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/auth/login.sh + pkgctl_auth_login "$@" + exit 0 + ;; + status) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/auth/status.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/auth/status.sh + pkgctl_auth_status "$@" + exit 0 + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + die "invalid command: %s" "$1" + ;; + esac + done +} diff --git a/src/lib/auth/login.sh b/src/lib/auth/login.sh new file mode 100644 index 0000000..d427676 --- /dev/null +++ b/src/lib/auth/login.sh @@ -0,0 +1,101 @@ +#!/hint/bash +# +# This may be included with or without `set -euE` +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_AUTH_LOGIN_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_AUTH_LOGIN_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/config.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/config.sh +# shellcheck source=src/lib/api/gitlab.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh + +set -e + + +pkgctl_auth_login_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] + + Interactively authenticate with the GitLab instance. + + The minimum required scopes for the token are: 'api', 'write_repository'. + + The GitLab API token can either be stored in a plaintext file, or + supplied via the DEVTOOLS_GITLAB_TOKEN environment variable using a + vault, see pkgctl-auth-login(1) for details. + + OPTIONS + -g, --gen-access-token Open the URL to generate a new personal access token + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} + $ ${COMMAND} --gen-access-token +_EOF_ +} + + +pkgctl_auth_login() { + local token personal_access_token_url + local GEN_ACESS_TOKEN=0 + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_auth_login_usage + exit 0 + ;; + -g|--gen-access-token) + GEN_ACESS_TOKEN=1 + shift + ;; + *) + die "invalid argument: %s" "$1" + ;; + esac + done + + personal_access_token_url="https://${GITLAB_HOST}/-/profile/personal_access_tokens?name=pkgctl+token&scopes=api,write_repository" + + cat <<- _EOF_ + Logging into ${BOLD}${GITLAB_HOST}${ALL_OFF} + + Tip: you can generate a Personal Access Token here ${personal_access_token_url} + The minimum required scopes are 'api' and 'write_repository'. + + If you do not want to store the token in a plaintext file, you can abort + the following prompt and supply the token via the DEVTOOLS_GITLAB_TOKEN + environment variable using a vault, see pkgctl-auth-login(1) for details. +_EOF_ + + if (( GEN_ACESS_TOKEN )); then + xdg-open "${personal_access_token_url}" 2>/dev/null + fi + + # read token from stdin + read -s -r -p "${GREEN}?${ALL_OFF} ${BOLD}Paste your authentication token:${ALL_OFF} " token + echo + + if [[ -z ${token} ]]; then + msg_error " No token provided" + exit 1 + fi + + # check if the passed token works + GITLAB_TOKEN="${token}" + if ! result=$(gitlab_api_get_user); then + printf "%s\n" "$result" + exit 1 + fi + + msg_success " Logged in as ${BOLD}${result}${ALL_OFF}" + save_devtools_config +} diff --git a/src/lib/auth/status.sh b/src/lib/auth/status.sh new file mode 100644 index 0000000..6cbaab1 --- /dev/null +++ b/src/lib/auth/status.sh @@ -0,0 +1,69 @@ +#!/hint/bash +# +# This may be included with or without `set -euE` +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_AUTH_STATUS_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_AUTH_STATUS_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/api/gitlab.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh + +set -e + + +pkgctl_auth_status_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] + + Verifies and displays information about your authentication state of + services like the GitLab instance and reports issues if any. + + OPTIONS + -t, --show-token Display the auth token + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} + $ ${COMMAND} --show-token +_EOF_ +} + +pkgctl_auth_status() { + local SHOW_TOKEN=0 + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_auth_status_usage + exit 0 + ;; + -t|--show-token) + SHOW_TOKEN=1 + shift + ;; + *) + die "invalid argument: %s" "$1" + ;; + esac + done + + printf "%s\n" "${BOLD}${GITLAB_HOST}${ALL_OFF}" + # shellcheck disable=2119 + if ! username=$(gitlab_api_get_user); then + printf "%s\n" "${username}" + exit 1 + fi + + msg_success " Logged in as ${BOLD}${username}${ALL_OFF}" + if (( SHOW_TOKEN )); then + msg_success " Token: ${GITLAB_TOKEN}" + else + msg_success " Token: **************************" + fi +} diff --git a/src/lib/build/build.sh b/src/lib/build/build.sh new file mode 100644 index 0000000..3394395 --- /dev/null +++ b/src/lib/build/build.sh @@ -0,0 +1,420 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_BUILD_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_BUILD_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/db/update.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/update.sh +# shellcheck source=src/lib/release.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/release.sh +# shellcheck source=src/lib/util/git.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/git.sh +# shellcheck source=src/lib/util/pacman.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/pacman.sh +# shellcheck source=src/lib/valid-repos.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh +# shellcheck source=src/lib/valid-tags.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-tags.sh + +source /usr/share/makepkg/util/config.sh +source /usr/share/makepkg/util/message.sh + +set -e +set -o pipefail + + +pkgctl_build_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [PATH]... + + Build packages inside a clean chroot + + When a new pkgver is set using the appropriate PKGBUILD options the + checksums are automatically updated. + + TODO + + BUILD OPTIONS + --arch ARCH Specify architectures to build for (disables auto-detection) + --repo REPO Specify a target repository (disables auto-detection) + -s, --staging Build against the staging counterpart of the auto-detected repo + -t, --testing Build against the testing counterpart of the auto-detected repo + -o, --offload Build on a remote server and transfer artifacts afterwards + -c, --clean Recreate the chroot before building + -I, --install FILE Install a package into the working copy of the chroot + -w, --worker SLOT Name of the worker slot, useful for concurrent builds (disables automatic names) + --nocheck Do not run the check() function in the PKGBUILD + + PKGBUILD OPTIONS + --pkgver=PKGVER Set pkgver, reset pkgrel and update checksums + --pkgrel=PKGREL Set pkgrel to a given value + --rebuild Increment the current pkgrel variable + -e, --edit Edit the PKGBUILD before building + + RELEASE OPTIONS + -r, --release Automatically commit, tag and release after building + -m, --message MSG Use the given <msg> as the commit message + -u, --db-update Automatically update the pacman database as last action + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} + $ ${COMMAND} --rebuild --staging --message 'libyay 0.42 rebuild' libfoo libbar + $ ${COMMAND} --pkgver 1.42 --release --db-update +_EOF_ +} + +pkgctl_build_check_option_group_repo() { + local option=$1 + local repo=$2 + local testing=$3 + local staging=$4 + if ( (( testing )) && (( staging )) ) || + ( [[ $repo =~ ^.*-(staging|testing)$ ]] && ( (( testing )) || (( staging )) )); then + die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}" + exit 1 + fi + return 0 +} + +pkgctl_build_check_option_group_ver() { + local option=$1 + local pkgver=$2 + local pkgrel=$3 + local rebuild=$4 + if [[ -n "${pkgver}" ]] || [[ -n "${pkgrel}" ]] || (( rebuild )); then + die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}" + exit 1 + fi + return 0 +} + +# TODO: import pgp keys +pkgctl_build() { + if (( $# < 1 )) && [[ ! -f PKGBUILD ]]; then + pkgctl_build_usage + exit 1 + fi + + local UPDPKGSUMS=0 + local EDIT=0 + local REBUILD=0 + local OFFLOAD=0 + local STAGING=0 + local TESTING=0 + local RELEASE=0 + local DB_UPDATE=0 + + local REPO= + local PKGVER= + local PKGREL= + local MESSAGE= + + local paths=() + local BUILD_ARCH=() + local BUILD_OPTIONS=() + local MAKECHROOT_OPTIONS=() + local RELEASE_OPTIONS=() + local MAKEPKG_OPTIONS=() + + local WORKER= + local WORKER_SLOT= + + # variables + local path pkgbase pkgrepo source + + while (( $# )); do + case $1 in + -h|--help) + pkgctl_build_usage + exit 0 + ;; + --repo) + (( $# <= 1 )) && die "missing argument for %s" "$1" + REPO="${2}" + pkgctl_build_check_option_group_repo '--repo' "${REPO}" "${TESTING}" "${STAGING}" + shift 2 + ;; + --arch) + (( $# <= 1 )) && die "missing argument for %s" "$1" + if [[ ${2} == all ]]; then + BUILD_ARCH=("${_arch[@]::${#_arch[@]}-1}") + elif [[ ${2} == any ]]; then + BUILD_ARCH=("${_arch[0]}") + elif ! in_array "${2}" "${BUILD_ARCH[@]}"; then + if ! in_array "${2}" "${_arch[@]}"; then + die 'invalid architecture: %s' "${2}" + fi + BUILD_ARCH+=("${2}") + fi + shift 2 + ;; + --pkgver=*) + pkgctl_build_check_option_group_ver '--pkgver' "${PKGVER}" "${PKGREL}" "${REBUILD}" + PKGVER="${1#*=}" + PKGREL=1 + UPDPKGSUMS=1 + shift + ;; + --pkgrel=*) + pkgctl_build_check_option_group_ver '--pkgrel' "${PKGVER}" "${PKGREL}" "${REBUILD}" + PKGREL="${1#*=}" + shift + ;; + --rebuild) + # shellcheck source=src/lib/util/git.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/git.sh + pkgctl_build_check_option_group_ver '--rebuild' "${PKGVER}" "${PKGREL}" "${REBUILD}" + REBUILD=1 + shift + ;; + -e|--edit) + EDIT=1 + shift + ;; + -o|--offload) + OFFLOAD=1 + shift + ;; + -s|--staging) + STAGING=1 + pkgctl_build_check_option_group_repo '--staging' "${REPO}" "${TESTING}" "${STAGING}" + shift + ;; + -t|--testing) + TESTING=1 + pkgctl_build_check_option_group_repo '--testing' "${REPO}" "${TESTING}" "${STAGING}" + shift + ;; + -c|--clean) + BUILD_OPTIONS+=("-c") + shift + ;; + -I|--install) + (( $# <= 1 )) && die "missing argument for %s" "$1" + MAKECHROOT_OPTIONS+=("-I" "$2") + warning 'installing packages into the chroot may break reproducible builds, use with caution!' + shift 2 + ;; + --nocheck) + MAKEPKG_OPTIONS+=("--nocheck") + warning 'not running checks is disallowed for official packages, except for bootstrapping. Please rebuild after bootstrapping is completed!' + shift + ;; + -r|--release) + # shellcheck source=src/lib/release.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/release.sh + RELEASE=1 + shift + ;; + -m|--message) + (( $# <= 1 )) && die "missing argument for %s" "$1" + MESSAGE=$2 + RELEASE_OPTIONS+=("--message" "${MESSAGE}") + shift 2 + ;; + -u|--db-update) + DB_UPDATE=1 + shift + ;; + -w|--worker) + (( $# <= 1 )) && die "missing argument for %s" "$1" + WORKER_SLOT=$2 + shift 2 + ;; + --) + shift + break + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + paths=("$@") + break + ;; + esac + done + + # check if any release specific options were specified without releasing + if (( ! RELEASE )); then + if (( DB_UPDATE )); then + die "cannot use --db-update without --release" + fi + if [[ -n "${MESSAGE}" ]]; then + die "cannot use --message without --release" + fi + fi + + # check if invoked without any path from within a packaging repo + if (( ${#paths[@]} == 0 )); then + if [[ -f PKGBUILD ]]; then + paths=(".") + else + pkgctl_build_usage + exit 1 + fi + fi + + # assign default worker slot + if [[ -z ${WORKER_SLOT} ]] && ! WORKER_SLOT="$(tty | sed 's|/dev/pts/||')"; then + WORKER_SLOT=$(( RANDOM % $(nproc) + 1 )) + fi + WORKER="${USER}-${WORKER_SLOT}" + + # Update pacman cache for auto-detection + if [[ -z ${REPO} ]]; then + update_pacman_repo_cache + # Check valid repos if not resolved dynamically + elif ! in_array "${REPO}" "${_repos[@]}"; then + die "Invalid repository target: %s" "${REPO}" + fi + + for path in "${paths[@]}"; do + pushd "${path}" >/dev/null + + if [[ ! -f PKGBUILD ]]; then + die 'PKGBUILD not found in %s' "${path}" + fi + + source=() + # shellcheck source=contrib/makepkg/PKGBUILD.proto + . ./PKGBUILD + pkgbase=${pkgbase:-$pkgname} + pkgrepo=${REPO} + msg "Building ${pkgbase}" + + # auto-detection of build target + if [[ -z ${pkgrepo} ]]; then + if ! pkgrepo=$(get_pacman_repo_from_pkgbuild PKGBUILD); then + die 'failed to get pacman repo' + fi + if [[ -z "${pkgrepo}" ]]; then + die 'unknown repo, specify --repo for packages not currently in any official repo' + fi + fi + + # special cases to resolve final build target + if (( TESTING )); then + pkgrepo="${pkgrepo}-testing" + elif (( STAGING )); then + pkgrepo="${pkgrepo}-staging" + elif [[ $pkgrepo == core ]]; then + pkgrepo="${pkgrepo}-testing" + fi + + # auto-detection of build architecture + if [[ $pkgrepo = multilib* ]]; then + BUILD_ARCH=("") + elif (( ${#BUILD_ARCH[@]} == 0 )); then + if in_array any "${arch[@]}"; then + BUILD_ARCH=("${_arch[0]}") + else + BUILD_ARCH+=("${arch[@]}") + fi + fi + + # print gathered build modes + msg2 " repo: ${pkgrepo}" + msg2 " arch: ${BUILD_ARCH[*]}" + msg2 "worker: ${WORKER}" + + # increment pkgrel on rebuild + if (( REBUILD )); then + # try to figure out of pkgrel has been changed + if ! old_pkgrel=$(git_diff_tree HEAD PKGBUILD | grep --perl-regexp --only-matching --max-count=1 '^-pkgrel=\K\w+'); then + old_pkgrel=${pkgrel} + fi + # check if pkgrel conforms expectations + [[ ${pkgrel/.*} =~ ^[0-9]+$ ]] || die "Non-standard pkgrel declaration" + [[ ${old_pkgrel/.*} =~ ^[0-9]+$ ]] || die "Non-standard pkgrel declaration" + # increment pkgrel if it hasn't been changed yet + if [[ ${pkgrel} = "${old_pkgrel}" ]]; then + PKGREL=$((${pkgrel/.*}+1)) + else + warning 'ignoring --rebuild as pkgrel has already been incremented from %s to %s' "${old_pkgrel}" "${pkgrel}" + fi + fi + + # update pkgver + if [[ -n ${PKGVER} ]]; then + if [[ $(type -t pkgver) == function ]]; then + # TODO: check if die or warn, if we provide _commit _gitcommit setter maybe? + warning 'setting pkgver variable has no effect if the PKGBUILD has a pkgver() function' + fi + msg "Bumping pkgver to ${PKGVER}" + grep --extended-regexp --quiet --max-count=1 "^pkgver=${pkgver}$" PKGBUILD || die "Non-standard pkgver declaration" + sed --regexp-extended "s|^(pkgver=)${pkgver}$|\1${PKGVER}|g" -i PKGBUILD + fi + + # update pkgrel + if [[ -n ${PKGREL} ]]; then + msg "Bumping pkgrel to ${PKGREL}" + grep --extended-regexp --quiet --max-count=1 "^pkgrel=${pkgrel}$" PKGBUILD || die "Non-standard pkgrel declaration" + sed --regexp-extended "s|^(pkgrel=)${pkgrel}$|\1${PKGREL}|g" -i PKGBUILD + fi + + # edit PKGBUILD + if (( EDIT )); then + stat_busy 'Editing PKGBUILD' + if [[ -n $GIT_EDITOR ]]; then + $GIT_EDITOR PKGBUILD || die + elif [[ -n $VISUAL ]]; then + $VISUAL PKGBUILD || die + elif [[ -n $EDITOR ]]; then + $EDITOR PKGBUILD || die + elif giteditor=$(git config --get core.editor); then + $giteditor PKGBUILD || die + else + die "No usable editor found (tried \$GIT_EDITOR, \$VISUAL, \$EDITOR, git config [core.editor])." + fi + stat_done + fi + + + # update checksums if any sources are declared + if (( UPDPKGSUMS )) && (( ${#source[@]} >= 1 )); then + updpkgsums + fi + + # execute build + for arch in "${BUILD_ARCH[@]}"; do + if [[ -n $arch ]]; then + msg "Building ${pkgbase} for [${pkgrepo}] (${arch})" + BUILDTOOL="${pkgrepo}-${arch}-build" + else + msg "Building ${pkgbase} for [${pkgrepo}]" + BUILDTOOL="${pkgrepo}-build" + fi + + if (( OFFLOAD )); then + offload-build --repo "${pkgrepo}" -- "${BUILD_OPTIONS[@]}" -- "${MAKECHROOT_OPTIONS[@]}" -l "${WORKER}" -- "${MAKEPKG_OPTIONS[@]}" + else + "${BUILDTOOL}" "${BUILD_OPTIONS[@]}" -- "${MAKECHROOT_OPTIONS[@]}" -l "${WORKER}" -- "${MAKEPKG_OPTIONS[@]}" + fi + done + + # release the build + if (( RELEASE )); then + pkgctl_release --repo "${pkgrepo}" "${RELEASE_OPTIONS[@]}" + fi + + # reset common PKGBUILD variables + unset pkgbase pkgname arch pkgrepo source pkgver pkgrel validpgpkeys + popd >/dev/null + done + + # update the binary package repo db as last action + if (( RELEASE )) && (( DB_UPDATE )); then + # shellcheck disable=2119 + pkgctl_db_update + fi +} diff --git a/src/lib/common.sh b/src/lib/common.sh new file mode 100644 index 0000000..3d1ee56 --- /dev/null +++ b/src/lib/common.sh @@ -0,0 +1,313 @@ +#!/hint/bash +# +# This may be included with or without `set -euE` +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_COMMON_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_COMMON_SH="$(set +o|grep nounset)" + +set +u +o posix +# shellcheck disable=1091 +. /usr/share/makepkg/util.sh +$DEVTOOLS_INCLUDE_COMMON_SH + +# Avoid any encoding problems +export LANG=C + +# Set buildtool properties +export BUILDTOOL=devtools +export BUILDTOOLVER=@buildtoolver@ + +# Set common properties +export PACMAN_KEYRING_DIR=/etc/pacman.d/gnupg +export GITLAB_HOST=gitlab.archlinux.org +export GIT_REPO_SPEC_VERSION=1 +export GIT_PACKAGING_NAMESPACE=archlinux/packaging/packages +export GIT_PACKAGING_NAMESPACE_ID=11323 +export GIT_PACKAGING_URL_SSH="git@${GITLAB_HOST}:${GIT_PACKAGING_NAMESPACE}" +export GIT_PACKAGING_URL_HTTPS="https://${GITLAB_HOST}/${GIT_PACKAGING_NAMESPACE}" +export PACKAGING_REPO_RELEASE_HOST=repos.archlinux.org + +# check if messages are to be printed using color +if [[ -t 2 && "$TERM" != dumb ]] || [[ ${DEVTOOLS_COLOR} == always ]]; then + colorize +else + # shellcheck disable=2034 + declare -gr ALL_OFF='' BOLD='' BLUE='' GREEN='' RED='' YELLOW='' +fi + +stat_busy() { + local mesg=$1; shift + # shellcheck disable=2059 + printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}...${ALL_OFF}" "$@" >&2 +} + +stat_progress() { + # shellcheck disable=2059 + printf "${BOLD}.${ALL_OFF}" >&2 +} + +stat_done() { + # shellcheck disable=2059 + printf "${BOLD}done${ALL_OFF}\n" >&2 +} + +msg_success() { + local msg=$1 + local padding + padding=$(echo "${msg}"|sed -E 's/( *).*/\1/') + msg=$(echo "${msg}"|sed -E 's/ *(.*)/\1/') + printf "%s %s\n" "${padding}${GREEN}✓${ALL_OFF}" "${msg}" >&2 +} + +msg_error() { + local msg=$1 + local padding + padding=$(echo "${msg}"|sed -E 's/( *).*/\1/') + msg=$(echo "${msg}"|sed -E 's/ *(.*)/\1/') + printf "%s %s\n" "${padding}${RED}x${ALL_OFF}" "${msg}" >&2 +} + +msg_warn() { + local msg=$1 + local padding + padding=$(echo "${msg}"|sed -E 's/( *).*/\1/') + msg=$(echo "${msg}"|sed -E 's/ *(.*)/\1/') + printf "%s %s\n" "${padding}${YELLOW}!${ALL_OFF}" "${msg}" >&2 +} + +_setup_workdir=false +setup_workdir() { + [[ -z ${WORKDIR:-} ]] && WORKDIR=$(mktemp -d --tmpdir "${0##*/}.XXXXXXXXXX") + _setup_workdir=true + trap 'trap_abort' INT QUIT TERM HUP + trap 'trap_exit' EXIT +} + +cleanup() { + if [[ -n ${WORKDIR:-} ]] && $_setup_workdir; then + rm -rf "$WORKDIR" + fi + exit "${1:-0}" +} + +abort() { + error 'Aborting...' + cleanup 255 +} + +trap_abort() { + trap - EXIT INT QUIT TERM HUP + abort +} + +trap_exit() { + local r=$? + trap - EXIT INT QUIT TERM HUP + cleanup $r +} + +die() { + (( $# )) && error "$@" + cleanup 255 +} + +## +# usage : lock( $fd, $file, $message, [ $message_arguments... ] ) +## +lock() { + # Only reopen the FD if it wasn't handed to us + if ! [[ "/dev/fd/$1" -ef "$2" ]]; then + mkdir -p -- "$(dirname -- "$2")" + eval "exec $1>"'"$2"' + fi + + if ! flock -n "$1"; then + stat_busy "${@:3}" + flock "$1" + stat_done + fi +} + +## +# usage : slock( $fd, $file, $message, [ $message_arguments... ] ) +## +slock() { + # Only reopen the FD if it wasn't handed to us + if ! [[ "/dev/fd/$1" -ef "$2" ]]; then + mkdir -p -- "$(dirname -- "$2")" + eval "exec $1>"'"$2"' + fi + + if ! flock -sn "$1"; then + stat_busy "${@:3}" + flock -s "$1" + stat_done + fi +} + +## +# usage : lock_close( $fd ) +## +lock_close() { + local fd=$1 + # https://github.com/koalaman/shellcheck/issues/862 + # shellcheck disable=2034 + exec {fd}>&- +} + +## +# usage: pkgver_equal( $pkgver1, $pkgver2 ) +## +pkgver_equal() { + if [[ $1 = *-* && $2 = *-* ]]; then + # if both versions have a pkgrel, then they must be an exact match + [[ $1 = "$2" ]] + else + # otherwise, trim any pkgrel and compare the bare version. + [[ ${1%%-*} = "${2%%-*}" ]] + fi +} + +## +# usage: find_cached_package( $pkgname, $pkgver, $arch ) +# +# $pkgver can be supplied with or without a pkgrel appended. +# If not supplied, any pkgrel will be matched. +## +shopt -s extglob +find_cached_package() { + local searchdirs=("$PWD" "$PKGDEST") results=() + local targetname=$1 targetver=$2 targetarch=$3 + local dir pkg packages pkgbasename name ver rel arch r results + + for dir in "${searchdirs[@]}"; do + [[ -d $dir ]] || continue + + shopt -s extglob nullglob + mapfile -t packages < <(printf "%s\n" "$dir"/"${targetname}"-"${targetver}"-*"${targetarch}".pkg.tar?(.!(sig|*.*))) + shopt -u extglob nullglob + + for pkg in "${packages[@]}"; do + [[ -f $pkg ]] || continue + + # avoid adding duplicates of the same inode + for r in "${results[@]}"; do + [[ $r -ef $pkg ]] && continue 2 + done + + # split apart package filename into parts + pkgbasename=${pkg##*/} + pkgbasename=${pkgbasename%.pkg.tar*} + + arch=${pkgbasename##*-} + pkgbasename=${pkgbasename%-"$arch"} + + rel=${pkgbasename##*-} + pkgbasename=${pkgbasename%-"$rel"} + + ver=${pkgbasename##*-} + name=${pkgbasename%-"$ver"} + + if [[ $targetname = "$name" && $targetarch = "$arch" ]] && + pkgver_equal "$targetver" "$ver-$rel"; then + results+=("$pkg") + fi + done + done + + case ${#results[*]} in + 0) + return 1 + ;; + 1) + printf '%s\n' "${results[0]}" + return 0 + ;; + *) + error 'Multiple packages found:' + printf '\t%s\n' "${results[@]}" >&2 + return 1 + esac +} +shopt -u extglob + +check_package_validity(){ + local pkgfile=$1 + if grep -q "packager = Unknown Packager" <(bsdtar -xOqf "$pkgfile" .PKGINFO); then + die "PACKAGER was not set when building package" + fi + hashsum=sha256sum + pkgbuild_hash=$(awk -v"hashsum=$hashsum" -F' = ' '$1 == "pkgbuild_"hashsum {print $2}' <(bsdtar -xOqf "$pkgfile" .BUILDINFO)) + if [[ "$pkgbuild_hash" != "$($hashsum PKGBUILD|cut -d' ' -f1)" ]]; then + die "PKGBUILD $hashsum mismatch: expected $pkgbuild_hash" + fi +} + + +# usage: grep_pkginfo pkgfile pattern +grep_pkginfo() { + local _ret=() + mapfile -t _ret < <(bsdtar -xOqf "$1" ".PKGINFO" | grep "^${2} = ") + printf '%s\n' "${_ret[@]#${2} = }" +} + + +# Get the package name +getpkgname() { + local _name + + _name="$(grep_pkginfo "$1" "pkgname")" + if [[ -z $_name ]]; then + error "Package '%s' has no pkgname in the PKGINFO. Fail!" "$1" + exit 1 + fi + + echo "$_name" +} + + +# Get the package base or name as fallback +getpkgbase() { + local _base + + _base="$(grep_pkginfo "$1" "pkgbase")" + if [[ -z $_base ]]; then + getpkgname "$1" + else + echo "$_base" + fi +} + + +getpkgdesc() { + local _desc + + _desc="$(grep_pkginfo "$1" "pkgdesc")" + if [[ -z $_desc ]]; then + error "Package '%s' has no pkgdesc in the PKGINFO. Fail!" "$1" + exit 1 + fi + + echo "$_desc" +} + + +get_tag_from_pkgver() { + local pkgver=$1 + local tag=${pkgver} + + tag=${tag/:/-} + tag=${tag//~/.} + echo "${tag}" +} + + +is_debug_package() { + local pkgfile=${1} pkgbase pkgname pkgdesc + pkgbase="$(getpkgbase "${pkgfile}")" + pkgname="$(getpkgname "${pkgfile}")" + pkgdesc="$(getpkgdesc "${pkgfile}")" + [[ ${pkgdesc} == "Detached debugging symbols for "* && ${pkgbase}-debug = "${pkgname}" ]] +} diff --git a/src/lib/config.sh b/src/lib/config.sh new file mode 100644 index 0000000..b09479a --- /dev/null +++ b/src/lib/config.sh @@ -0,0 +1,48 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_CONFIG_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_CONFIG_SH=1 + +set -e + +readonly XDG_DEVTOOLS_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/devtools" +readonly XDG_DEVTOOLS_GITLAB_CONFIG="${XDG_DEVTOOLS_DIR}/gitlab.conf" + +# default config variables +export GITLAB_TOKEN="" + +load_devtools_config() { + # temporary permission fixup + if [[ -d "${XDG_DEVTOOLS_DIR}" ]]; then + chmod 700 "${XDG_DEVTOOLS_DIR}" + fi + if [[ -f "${XDG_DEVTOOLS_GITLAB_CONFIG}" ]]; then + chmod 600 "${XDG_DEVTOOLS_GITLAB_CONFIG}" + fi + if [[ -n "${DEVTOOLS_GITLAB_TOKEN}" ]]; then + GITLAB_TOKEN="${DEVTOOLS_GITLAB_TOKEN}" + return + fi + if [[ -f "${XDG_DEVTOOLS_GITLAB_CONFIG}" ]]; then + GITLAB_TOKEN=$(grep GITLAB_TOKEN "${XDG_DEVTOOLS_GITLAB_CONFIG}"|cut -d= -f2|cut -d\" -f2) + return + fi + GITLAB_TOKEN="" +} + +save_devtools_config() { + # temporary permission fixup + if [[ -d "${XDG_DEVTOOLS_DIR}" ]]; then + chmod 700 "${XDG_DEVTOOLS_DIR}" + fi + if [[ -f "${XDG_DEVTOOLS_GITLAB_CONFIG}" ]]; then + chmod 600 "${XDG_DEVTOOLS_GITLAB_CONFIG}" + fi + ( + umask 0077 + mkdir -p "${XDG_DEVTOOLS_DIR}" + printf 'GITLAB_TOKEN="%s"\n' "${GITLAB_TOKEN}" > "${XDG_DEVTOOLS_GITLAB_CONFIG}" + ) +} diff --git a/src/lib/db.sh b/src/lib/db.sh new file mode 100644 index 0000000..397ff0d --- /dev/null +++ b/src/lib/db.sh @@ -0,0 +1,80 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_DB_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_DB_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} + +set -e + + +pkgctl_db_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [COMMAND] [OPTIONS] + + Pacman database modification for packge update, move etc + + COMMANDS + move Move packages between pacman repositories + remove Remove packages from pacman repositories + update Update the pacman database as final release step + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} move extra-staging extra-testing libfoo libbar + $ ${COMMAND} remove core-testing libfoo libbar + $ ${COMMAND} update +_EOF_ +} + +pkgctl_db() { + if (( $# < 1 )); then + pkgctl_db_usage + exit 0 + fi + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_db_usage + exit 0 + ;; + move) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/db/move.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/move.sh + pkgctl_db_move "$@" + exit 0 + ;; + remove) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/db/remove.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/remove.sh + pkgctl_db_remove "$@" + exit 0 + ;; + update) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/db/update.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/update.sh + pkgctl_db_update "$@" + exit 0 + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + die "invalid command: %s" "$1" + ;; + esac + done +} diff --git a/src/lib/db/move.sh b/src/lib/db/move.sh new file mode 100644 index 0000000..825b350 --- /dev/null +++ b/src/lib/db/move.sh @@ -0,0 +1,64 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_DB_MOVE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_DB_MOVE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + +set -e + + +pkgctl_db_move_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [SOURCE_REPO] [TARGET_REPO] [PKGBASE]... + + Move packages between binary repositories. + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} extra-staging extra-testing libfoo libbar + $ ${COMMAND} extra core libfoo libbar +_EOF_ +} + +pkgctl_db_move() { + local SOURCE_REPO="" + local TARGET_REPO="" + local PKGBASES=() + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_db_move_usage + exit 0 + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + break + ;; + esac + done + + if (( $# < 3 )); then + pkgctl_db_move_usage + exit 1 + fi + + SOURCE_REPO=$1 + TARGET_REPO=$2 + shift 2 + PKGBASES+=("$@") + + # shellcheck disable=SC2029 + ssh "${PACKAGING_REPO_RELEASE_HOST}" db-move "${SOURCE_REPO}" "${TARGET_REPO}" "${PKGBASES[@]}" +} diff --git a/src/lib/db/remove.sh b/src/lib/db/remove.sh new file mode 100644 index 0000000..ba21c83 --- /dev/null +++ b/src/lib/db/remove.sh @@ -0,0 +1,69 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_DB_REMOVE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_DB_REMOVE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + +set -e + + +pkgctl_db_remove_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [REPO] [PKGBASE]... + + Remove packages from binary repositories. + + OPTIONS + -a, --arch Override the architecture (disables auto-detection) + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} core-testing libfoo libbar + $ ${COMMAND} --arch x86_64 core libyay +_EOF_ +} + +pkgctl_db_remove() { + local REPO="" + local ARCH=any + local PKGBASES=() + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_db_remove_usage + exit 0 + ;; + -a|--arch) + (( $# <= 1 )) && die "missing argument for %s" "$1" + ARCH=$2 + shift 2 + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + break + ;; + esac + done + + if (( $# < 2 )); then + pkgctl_db_remove_usage + exit 1 + fi + + REPO=$1 + shift + PKGBASES+=("$@") + + # shellcheck disable=SC2029 + ssh "${PACKAGING_REPO_RELEASE_HOST}" db-remove "${REPO}" "${ARCH}" "${PKGBASES[@]}" +} diff --git a/src/lib/db/update.sh b/src/lib/db/update.sh new file mode 100644 index 0000000..269720d --- /dev/null +++ b/src/lib/db/update.sh @@ -0,0 +1,46 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_DB_UPDATE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_DB_UPDATE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + +set -e + + +pkgctl_db_update_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] + + Update the binary repository as final release step for packages that + have been transfered and staged on ${PACKAGING_REPO_RELEASE_HOST}. + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} +_EOF_ +} + +pkgctl_db_update() { + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_db_update_usage + exit 0 + ;; + *) + die "invalid argument: %s" "$1" + ;; + esac + done + + ssh "${PACKAGING_REPO_RELEASE_HOST}" db-update +} diff --git a/src/lib/release.sh b/src/lib/release.sh new file mode 100644 index 0000000..aabbd35 --- /dev/null +++ b/src/lib/release.sh @@ -0,0 +1,167 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_RELEASE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_RELEASE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/db/update.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db/update.sh +# shellcheck source=src/lib/util/pacman.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/util/pacman.sh +# shellcheck source=src/lib/valid-repos.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh + +source /usr/share/makepkg/util/util.sh + +set -e + + +pkgctl_release_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [PATH]... + + Release step to commit, tag and upload build artifacts + + Modified version controlled files will first be staged for commit, + afterwards a Git tag matching the pkgver will be created and finally + all build artifacts will be uploaded. + + By default the target pacman repository will be auto-detected by querying + the repo it is currently released in. When initially adding a new package + to the repositories, the target repo must be specified manually. + + OPTIONS + -m, --message MSG Use the given <msg> as the commit message + -r, --repo REPO Specify a target repository (disables auto-detection) + -s, --staging Release to the staging counterpart of the auto-detected repo + -t, --testing Release to the testing counterpart of the auto-detected repo + -u, --db-update Automatically update the pacman database after uploading + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} + $ ${COMMAND} --repo core-testing --message 'libyay 0.42 rebuild' libfoo libbar + $ ${COMMAND} --staging --db-update libfoo +_EOF_ +} + +pkgctl_release_check_option_group() { + local option=$1 + local repo=$2 + local testing=$3 + local staging=$4 + if [[ -n "${repo}" ]] || (( testing )) || (( staging )); then + die "The argument '%s' cannot be used with one or more of the other specified arguments" "${option}" + exit 1 + fi + return 0 +} + +pkgctl_release() { + if (( $# < 1 )) && [[ ! -f PKGBUILD ]]; then + pkgctl_release_usage + exit 1 + fi + + local MESSAGE="" + local PKGBASES=() + local REPO="" + local TESTING=0 + local STAGING=0 + local DB_UPDATE=0 + + local path pkgbase pkgnames repo repos + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_release_usage + exit 0 + ;; + -m|--message) + (( $# <= 1 )) && die "missing argument for %s" "$1" + MESSAGE=$2 + shift 2 + ;; + -r|--repo) + (( $# <= 1 )) && die "missing argument for %s" "$1" + pkgctl_release_check_option_group '--repo' "${REPO}" "${TESTING}" "${STAGING}" + REPO=$2 + shift 2 + ;; + -s|--staging) + pkgctl_release_check_option_group '--staging' "${REPO}" "${TESTING}" "${STAGING}" + STAGING=1 + shift + ;; + -t|--testing) + pkgctl_release_check_option_group '--testing' "${REPO}" "${TESTING}" "${STAGING}" + TESTING=1 + shift + ;; + -u|--db-update) + DB_UPDATE=1 + shift + ;; + -*) + die "invalid option: %s" "$1" + ;; + *) + PKGBASES+=("$@") + break + ;; + esac + done + + # Resolve package from current working directory + if (( 0 == ${#PKGBASES[@]} )); then + PKGBASES=("$PWD") + fi + + # Update pacman cache for auto-detection + if [[ -z ${REPO} ]]; then + update_pacman_repo_cache + # Check valid repos if not resolved dynamically + elif ! in_array "${REPO}" "${_repos[@]}"; then + die "Invalid repository target: %s" "${REPO}" + fi + + for path in "${PKGBASES[@]}"; do + pushd "${path}" >/dev/null + pkgbase=$(basename "${path}") + + if [[ -n ${REPO} ]]; then + repo=${REPO} + else + if ! repo=$(get_pacman_repo_from_pkgbuild PKGBUILD); then + die 'Failed to get pacman repo' + fi + if [[ -z "${repo}" ]]; then + die 'Unknown repo, please specify --repo for new packages' + fi + fi + + if (( TESTING )); then + repo="${repo}-testing" + elif (( STAGING )); then + repo="${repo}-staging" + elif [[ $repo == core ]]; then + repo="${repo}-testing" + fi + + msg "Releasing ${pkgbase} to ${repo}" + commitpkg "${repo}" "${MESSAGE}" + + unset repo + popd >/dev/null + done + + if (( DB_UPDATE )); then + # shellcheck disable=2119 + pkgctl_db_update + fi +} diff --git a/src/lib/repo.sh b/src/lib/repo.sh new file mode 100644 index 0000000..9f545e9 --- /dev/null +++ b/src/lib/repo.sh @@ -0,0 +1,110 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_REPO_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_REPO_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} + +set -e + + +pkgctl_repo_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [COMMAND] [OPTIONS] + + Manage Git packaging repositories and helps with their configuration + according to distro specs. + + Git author information and the used signing key is set up from + makepkg.conf read from any valid location like /etc or XDG_CONFIG_HOME. + The configure command can be used to synchronize the distro specs and + makepkg.conf settings for previously cloned repositories. + + The unprivileged option can be used for cloning packaging repositories + without SSH access using read-only HTTPS. + + COMMANDS + clone Clone a package repository + configure Configure a clone according to distro specs + create Create a new GitLab package repository + switch Switch a package repository to a specified version + web Open the packaging repository's website + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} clone libfoo linux libbar + $ ${COMMAND} clone --maintainer mynickname + $ ${COMMAND} configure * + $ ${COMMAND} create libfoo + $ ${COMMAND} switch 2:1.19.5-1 libfoo + $ ${COMMAND} web linux +_EOF_ +} + +pkgctl_repo() { + if (( $# < 1 )); then + pkgctl_repo_usage + exit 0 + fi + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_repo_usage + exit 0 + ;; + clone) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/repo/clone.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/clone.sh + pkgctl_repo_clone "$@" + exit 0 + ;; + configure) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/repo/configure.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/configure.sh + pkgctl_repo_configure "$@" + exit 0 + ;; + create) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/repo/create.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/create.sh + pkgctl_repo_create "$@" + exit 0 + ;; + switch) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/repo/switch.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/switch.sh + pkgctl_repo_switch "$@" + exit 0 + ;; + web) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/repo/web.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/web.sh + pkgctl_repo_web "$@" + exit 0 + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + die "invalid command: %s" "$1" + ;; + esac + done +} diff --git a/src/lib/repo/clone.sh b/src/lib/repo/clone.sh new file mode 100644 index 0000000..08bded4 --- /dev/null +++ b/src/lib/repo/clone.sh @@ -0,0 +1,199 @@ +#!/bin/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_REPO_CLONE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_REPO_CLONE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/api/gitlab.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh +# shellcheck source=src/lib/repo/configure.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/configure.sh + +source /usr/share/makepkg/util/message.sh + +set -e + + +pkgctl_repo_clone_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [PKGBASE]... + + Clone Git packaging repositories from the canonical namespace. + + The configure command is subsequently invoked to synchronize the distro + specs and makepkg.conf settings. The protocol option can be used + for cloning packaging repositories without SSH access using read-only + HTTPS. + + OPTIONS + -m, --maintainer=NAME Clone all packages of the named maintainer + --protocol https Clone the repository over https + --switch VERSION Switch the current working tree to a specified version + --universe Clone all existing packages, useful for cache warming + -j, --jobs N Run up to N jobs in parallel (default: $(nproc)) + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} libfoo linux libbar + $ ${COMMAND} --maintainer mynickname + $ ${COMMAND} --switch 1:1.0-2 libfoo +_EOF_ +} + +pkgctl_repo_clone() { + if (( $# < 1 )); then + pkgctl_repo_clone_usage + exit 0 + fi + + # options + local GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_SSH} + local CLONE_ALL=0 + local MAINTAINER= + local VERSION= + local CONFIGURE_OPTIONS=() + local jobs= + jobs=$(nproc) + + # variables + local command=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + local project_path + + while (( $# )); do + case $1 in + -h|--help) + pkgctl_repo_clone_usage + exit 0 + ;; + --protocol=https) + GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_HTTPS} + CONFIGURE_OPTIONS+=("$1") + shift + ;; + --protocol) + (( $# <= 1 )) && die "missing argument for %s" "$1" + if [[ $2 == https ]]; then + GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_HTTPS} + else + die "unsupported protocol: %s" "$2" + fi + CONFIGURE_OPTIONS+=("$1" "$2") + shift 2 + ;; + -m|--maintainer) + (( $# <= 1 )) && die "missing argument for %s" "$1" + MAINTAINER="$2" + shift 2 + ;; + --maintainer=*) + MAINTAINER="${1#*=}" + shift + ;; + --switch) + (( $# <= 1 )) && die "missing argument for %s" "$1" + # shellcheck source=src/lib/repo/switch.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/switch.sh + VERSION="$2" + shift 2 + ;; + --switch=*) + # shellcheck source=src/lib/repo/switch.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/switch.sh + VERSION="${1#*=}" + shift + ;; + --universe) + CLONE_ALL=1 + shift + ;; + -j|--jobs) + (( $# <= 1 )) && die "missing argument for %s" "$1" + jobs=$2 + shift 2 + ;; + --) + shift + break + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + pkgbases=("$@") + break + ;; + esac + done + + # Query packages of a maintainer + if [[ -n ${MAINTAINER} ]]; then + stat_busy "Query packages" + max_pages=$(curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name&maintainer=${MAINTAINER}" | jq -r '.num_pages') + if [[ ! ${max_pages} =~ ([[:digit:]]) ]]; then + stat_done + warning "found no packages for maintainer ${MAINTAINER}" + exit 0 + fi + mapfile -t pkgbases < <(for page in $(seq "${max_pages}"); do + curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name&maintainer=${MAINTAINER}&page=${page}" | jq -r '.results[].pkgbase' + stat_progress + done | sort --unique) + stat_done + fi + + # Query all released packages + if (( CLONE_ALL )); then + stat_busy "Query all released packages" + max_pages=$(curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name" | jq -r '.num_pages') + if [[ ! ${max_pages} =~ ([[:digit:]]) ]]; then + stat_done + die "failed to query packages" + fi + mapfile -t pkgbases < <(for page in $(seq "${max_pages}"); do + curl --silent --location --fail --retry 3 --retry-delay 3 "https://archlinux.org/packages/search/json/?sort=name&page=${page}" | jq -r '.results[].pkgbase' + stat_progress + done | sort --unique) + stat_done + fi + + # parallelization + if [[ ${jobs} != 1 ]] && (( ${#pkgbases[@]} > 1 )); then + # force colors in parallel if parent process is colorized + if [[ -n ${BOLD} ]]; then + export DEVTOOLS_COLOR=always + fi + # assign command options + if [[ -n "${VERSION}" ]]; then + command+=" --switch '${VERSION}'" + fi + if ! parallel --bar --jobs "${jobs}" "${command}" ::: "${pkgbases[@]}"; then + die 'Failed to clone some packages, please check the output' + exit 1 + fi + exit 0 + fi + + for pkgbase in "${pkgbases[@]}"; do + if [[ ! -d ${pkgbase} ]]; then + msg "Cloning ${pkgbase} ..." + project_path=$(gitlab_project_name_to_path "${pkgbase}") + remote_url="${GIT_REPO_BASE_URL}/${project_path}.git" + if ! git clone --origin origin "${remote_url}" "${pkgbase}"; then + die 'failed to clone %s' "${pkgbase}" + fi + else + warning "Skip cloning ${pkgbase}: Directory exists" + fi + + pkgctl_repo_configure "${CONFIGURE_OPTIONS[@]}" "${pkgbase}" + + if [[ -n "${VERSION}" ]]; then + pkgctl_repo_switch "${VERSION}" "${pkgbase}" + fi + done +} diff --git a/src/lib/repo/configure.sh b/src/lib/repo/configure.sh new file mode 100644 index 0000000..73300ae --- /dev/null +++ b/src/lib/repo/configure.sh @@ -0,0 +1,259 @@ +#!/bin/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_REPO_CONFIGURE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_REPO_CONFIGURE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/api/gitlab.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh + +source /usr/share/makepkg/util/config.sh +source /usr/share/makepkg/util/message.sh + +set -e + + +pkgctl_repo_configure_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [PATH]... + + Configure Git packaging repositories according to distro specs and + makepkg.conf settings. + + Git author information and the used signing key is set up from + makepkg.conf read from any valid location like /etc or XDG_CONFIG_HOME. + + The remote protocol is automatically determined from the author email + address by choosing SSH for all official packager identities and + read-only HTTPS otherwise. + + OPTIONS + --protocol https Configure remote url to use https + -j, --jobs N Run up to N jobs in parallel (default: $(nproc)) + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} * +_EOF_ +} + +get_packager_name() { + local packager=$1 + local packager_pattern="(.+) <(.+@.+)>" + local name + + if [[ ! $packager =~ $packager_pattern ]]; then + return 1 + fi + + name=$(echo "${packager}"|sed -E "s/${packager_pattern}/\1/") + printf "%s" "${name}" +} + +get_packager_email() { + local packager=$1 + local packager_pattern="(.+) <(.+@.+)>" + local email + + if [[ ! $packager =~ $packager_pattern ]]; then + return 1 + fi + + email=$(echo "${packager}"|sed -E "s/${packager_pattern}/\2/") + printf "%s" "${email}" +} + +is_packager_name_valid() { + local packager_name=$1 + if [[ -z ${packager_name} ]]; then + return 1 + elif [[ ${packager_name} == "John Doe" ]]; then + return 1 + elif [[ ${packager_name} == "Unknown Packager" ]]; then + return 1 + fi + return 0 +} + +is_packager_email_official() { + local packager_email=$1 + if [[ -z ${packager_email} ]]; then + return 1 + elif [[ $packager_email =~ .+@archlinux.org ]]; then + return 0 + fi + return 1 +} + +pkgctl_repo_configure() { + # options + local GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_HTTPS} + local official=0 + local proto=https + local proto_force=0 + local jobs= + jobs=$(nproc) + local paths=() + + # variables + local -r command=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + local path realpath pkgbase remote_url project_path + local PACKAGER GPGKEY packager_name packager_email + + while (( $# )); do + case $1 in + -h|--help) + pkgctl_repo_configure_usage + exit 0 + ;; + --protocol=https) + proto_force=1 + shift + ;; + --protocol) + (( $# <= 1 )) && die "missing argument for %s" "$1" + if [[ $2 == https ]]; then + proto_force=1 + else + die "unsupported protocol: %s" "$2" + fi + shift 2 + ;; + -j|--jobs) + (( $# <= 1 )) && die "missing argument for %s" "$1" + jobs=$2 + shift 2 + ;; + --) + shift + break + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + paths=("$@") + break + ;; + esac + done + + # check if invoked without any path from within a packaging repo + if (( ${#paths[@]} == 0 )); then + if [[ -f PKGBUILD ]]; then + paths=(".") + else + pkgctl_repo_configure_usage + exit 1 + fi + fi + + # Load makepkg.conf variables to be available for packager identity + msg "Collecting packager identity from makepkg.conf" + # shellcheck disable=2119 + load_makepkg_config + if [[ -n ${PACKAGER} ]]; then + if ! packager_name=$(get_packager_name "${PACKAGER}") || \ + ! packager_email=$(get_packager_email "${PACKAGER}"); then + die "invalid PACKAGER format '${PACKAGER}' in makepkg.conf" + fi + if ! is_packager_name_valid "${packager_name}"; then + die "invalid PACKAGER '${PACKAGER}' in makepkg.conf" + fi + if is_packager_email_official "${packager_email}"; then + official=1 + if (( ! proto_force )); then + proto=ssh + GIT_REPO_BASE_URL=${GIT_PACKAGING_URL_SSH} + fi + fi + fi + + msg2 "name : ${packager_name:-${YELLOW}undefined${ALL_OFF}}" + msg2 "email : ${packager_email:-${YELLOW}undefined${ALL_OFF}}" + msg2 "gpg-key : ${GPGKEY:-${YELLOW}undefined${ALL_OFF}}" + if [[ ${proto} == ssh ]]; then + msg2 "protocol: ${GREEN}${proto}${ALL_OFF}" + else + msg2 "protocol: ${YELLOW}${proto}${ALL_OFF}" + fi + + # parallelization + if [[ ${jobs} != 1 ]] && (( ${#paths[@]} > 1 )); then + if [[ -n ${BOLD} ]]; then + export DEVTOOLS_COLOR=always + fi + if ! parallel --bar --jobs "${jobs}" "${command}" ::: "${paths[@]}"; then + die 'Failed to configure some packages, please check the output' + exit 1 + fi + exit 0 + fi + + for path in "${paths[@]}"; do + if ! realpath=$(realpath -e "${path}"); then + die "No such directory: ${path}" + fi + + pkgbase=$(basename "${realpath}") + pkgbase=${pkgbase%.git} + msg "Configuring ${pkgbase}" + + if [[ ! -d "${path}/.git" ]]; then + die "Not a Git repository: ${path}" + fi + + pushd "${path}" >/dev/null + + project_path=$(gitlab_project_name_to_path "${pkgbase}") + remote_url="${GIT_REPO_BASE_URL}/${project_path}.git" + if ! git remote add origin "${remote_url}" &>/dev/null; then + git remote set-url origin "${remote_url}" + fi + + # move the master branch to main + if [[ $(git symbolic-ref --quiet --short HEAD) == master ]]; then + git branch --move main + git config branch.main.merge refs/heads/main + fi + + git config devtools.version "${GIT_REPO_SPEC_VERSION}" + git config pull.rebase true + git config branch.autoSetupRebase always + git config branch.main.remote origin + git config branch.main.rebase true + + git config transfer.fsckobjects true + git config fetch.fsckobjects true + git config receive.fsckobjects true + + # setup author identity + if [[ -n ${packager_name} ]]; then + git config user.name "${packager_name}" + git config user.email "${packager_email}" + fi + + # force gpg for official packagers + if (( official )); then + git config commit.gpgsign true + fi + + # set custom pgp key from makepkg.conf + if [[ -n $GPGKEY ]]; then + git config commit.gpgsign true + git config user.signingKey "${GPGKEY}" + fi + + if ! git ls-remote origin &>/dev/null; then + warning "configured remote origin may not exist, run:" + msg2 "pkgctl repo create ${pkgbase}" + fi + + popd >/dev/null + done +} diff --git a/src/lib/repo/create.sh b/src/lib/repo/create.sh new file mode 100644 index 0000000..31b46e1 --- /dev/null +++ b/src/lib/repo/create.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_REPO_CREATE_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_REPO_CREATE_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/api/gitlab.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh +# shellcheck source=src/lib/repo/clone.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/clone.sh +# shellcheck source=src/lib/repo/configure.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo/configure.sh + +set -e + + +pkgctl_repo_create_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [PKGBASE]... + + Create a new Git packaging repository in the canonical GitLab namespace. + + This command requires a valid GitLab API authentication. To setup a new + GitLab token or check the currently configured one please consult the + 'auth' subcommand for further instructions. + + If invoked without a parameter, try to create a packaging repository + based on the PKGBUILD from the current working directory and configure + the local repository afterwards. + + OPTIONS + -c, --clone Clone the Git repository after creation + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} libfoo +_EOF_ +} + +pkgctl_repo_create() { + # options + local pkgbases=() + local pkgbase + local clone=0 + local configure=0 + + # variables + local path + + while (( $# )); do + case $1 in + -h|--help) + pkgctl_repo_create_usage + exit 0 + ;; + -c|--clone) + clone=1 + shift + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + pkgbases=("$@") + break + ;; + esac + done + + # check if invoked without any path from within a packaging repo + if (( ${#pkgbases[@]} == 0 )); then + if [[ -f PKGBUILD ]]; then + if ! path=$(realpath -e .); then + die "failed to read path from current directory" + fi + pkgbases=("$(basename "${path}")") + clone=0 + configure=1 + else + pkgctl_repo_create_usage + exit 1 + fi + fi + + # create projects + for pkgbase in "${pkgbases[@]}"; do + if ! gitlab_api_create_project "${pkgbase}" >/dev/null; then + die "failed to create project: ${pkgbase}" + fi + msg_success "Successfully created ${pkgbase}" + if (( clone )); then + pkgctl_repo_clone "${pkgbase}" + elif (( configure )); then + pkgctl_repo_configure + fi + done + + # some convenience hints if not in auto clone/configure mode + if (( ! clone )) && (( ! configure )); then + cat <<- _EOF_ + + For new clones: + $(msg2 "pkgctl repo clone ${pkgbases[*]}") + For existing clones: + $(msg2 "pkgctl repo configure ${pkgbases[*]}") + _EOF_ + fi +} diff --git a/src/lib/repo/switch.sh b/src/lib/repo/switch.sh new file mode 100644 index 0000000..f411ac2 --- /dev/null +++ b/src/lib/repo/switch.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_REPO_SWITCH_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_REPO_SWITCH_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + +source /usr/share/makepkg/util/message.sh + +set -e + + +pkgctl_repo_switch_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [VERSION] [PKGBASE]... + + Switch a package source repository to a specified version, tag or + branch. The working tree and the index are updated to match the + specified ref. + + If a version identifier is specified in the pacman version format, that + identifier is automatically translated to the Git tag name accordingly. + + The current working directory is used if no PKGBASE is specified. + + OPTIONS + --discard-changes Discard changes if index or working tree is dirty + -f, --force An alias for --discard-changes + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} 1.14.6-1 gopass gopass-jsonapi + $ ${COMMAND} --force 2:1.19.5-1 + $ ${COMMAND} main +_EOF_ +} + +pkgctl_repo_switch() { + if (( $# < 1 )); then + pkgctl_repo_switch_usage + exit 0 + fi + + # options + local VERSION + local GIT_REF + local GIT_CHECKOUT_OPTIONS=() + local paths path realpath pkgbase + + while (( $# )); do + case $1 in + -h|--help) + pkgctl_repo_switch_usage + exit 0 + ;; + -f|--force|--discard-changes) + GIT_CHECKOUT_OPTIONS+=("--force") + shift + ;; + --) + shift + break + ;; + -*) + # - is special to switch back to previous version + if [[ $1 != - ]]; then + die "invalid argument: %s" "$1" + fi + ;;& + *) + if [[ -n ${VERSION} ]]; then + break + fi + VERSION=$1 + shift + ;; + esac + done + + if [[ -z ${VERSION} ]]; then + error "missing positional argument 'VERSION'" + pkgctl_repo_switch_usage + exit 1 + fi + + GIT_REF="$(get_tag_from_pkgver "${VERSION}")" + paths=("$@") + + # check if invoked without any path from within a packaging repo + if (( ${#paths[@]} == 0 )); then + if [[ -f PKGBUILD ]]; then + paths=(".") + else + die "Not a package repository: $(realpath -- .)" + fi + fi + + for path in "${paths[@]}"; do + if ! realpath=$(realpath -e -- "${path}"); then + die "No such directory: ${path}" + fi + pkgbase=$(basename "${realpath}") + + if [[ ! -d "${path}/.git" ]]; then + error "Not a Git repository: ${path}" + continue + fi + + if ! git -C "${path}" checkout "${GIT_CHECKOUT_OPTIONS[@]}" "${GIT_REF}"; then + die "Failed to switch ${pkgbase} to version ${VERSION}" + fi + msg "Successfully switched ${pkgbase} to version ${VERSION}" + done +} diff --git a/src/lib/repo/web.sh b/src/lib/repo/web.sh new file mode 100644 index 0000000..45ea53b --- /dev/null +++ b/src/lib/repo/web.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_REPO_WEB_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_REPO_WEB_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/api/gitlab.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/api/gitlab.sh + +set -e + + +pkgctl_repo_web_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] [PKGBASE]... + + Open the packaging repository's website via xdg-open. If called with + no arguments, open the package cloned in the current working directory. + + OPTIONS + -h, --help Show this help text + + EXAMPLES + $ ${COMMAND} linux +_EOF_ +} + +pkgctl_repo_web() { + local pkgbases=() + local path giturl pkgbase + + # option checking + while (( $# )); do + case $1 in + -h|--help) + pkgctl_repo_web_usage + exit 0 + ;; + --) + shift + break + ;; + -*) + die "invalid argument: %s" "$1" + ;; + *) + pkgbases=("$@") + break + ;; + esac + done + + # Check if web mode has xdg-open + if ! command -v xdg-open &>/dev/null; then + die "The web command requires 'xdg-open'" + fi + + # Check if used without pkgnames in a packaging directory + if (( ! $# )); then + path=${PWD} + if [[ ! -d "${path}/.git" ]]; then + die "Not a Git repository: ${path}" + fi + + giturl=$(git -C "${path}" remote get-url origin) + if [[ ${giturl} != *${GIT_PACKAGING_NAMESPACE}* ]]; then + die "Not a packaging repository: ${path}" + fi + + pkgbase=$(basename "${giturl}") + pkgbase=${pkgbase%.git} + pkgbases=("${pkgbase}") + fi + + for pkgbase in "${pkgbases[@]}"; do + path=$(gitlab_project_name_to_path "${pkgbase}") + xdg-open "${GIT_PACKAGING_URL_HTTPS}/${path}" + done +} diff --git a/src/lib/util/git.sh b/src/lib/util/git.sh new file mode 100644 index 0000000..c4af662 --- /dev/null +++ b/src/lib/util/git.sh @@ -0,0 +1,24 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_UTIL_GIT_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_UTIL_GIT_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} + + +git_diff_tree() { + local commit=$1 + local path=$2 + git \ + --no-pager \ + diff \ + --color=never \ + --color-moved=no \ + --unified=0 \ + --no-prefix \ + --no-ext-diff \ + "${commit}" \ + -- "${path}" +} diff --git a/src/lib/util/pacman.sh b/src/lib/util/pacman.sh new file mode 100644 index 0000000..f6c2d5f --- /dev/null +++ b/src/lib/util/pacman.sh @@ -0,0 +1,52 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_UTIL_PACMAN_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_UTIL_PACMAN_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh + +set -e + + +readonly _DEVTOOLS_PACMAN_CACHE_DIR=${XDG_CACHE_DIR:-$HOME/.cache}/devtools/pacman/db +readonly _DEVTOOLS_PACMAN_CONF_DIR=${_DEVTOOLS_LIBRARY_DIR}/pacman.conf.d +readonly _DEVTOOLS_MAKEPKG_CONF_DIR=${_DEVTOOLS_LIBRARY_DIR}/makepkg.conf.d + + +update_pacman_repo_cache() { + mkdir -p "${_DEVTOOLS_PACMAN_CACHE_DIR}" + msg "Updating pacman database cache" + lock 10 "${_DEVTOOLS_PACMAN_CACHE_DIR}.lock" "Locking pacman database cache" + fakeroot -- pacman --config "${_DEVTOOLS_PACMAN_CONF_DIR}/multilib.conf" \ + --dbpath "${_DEVTOOLS_PACMAN_CACHE_DIR}" \ + -Sy + lock_close 10 +} + +get_pacman_repo_from_pkgbuild() { + local path=${1:-PKGBUILD} + + # shellcheck source=contrib/makepkg/PKGBUILD.proto + mapfile -t pkgnames < <(source "${path}"; printf "%s\n" "${pkgname[@]}") + + if (( ${#pkgnames[@]} == 0 )); then + die 'Failed to get pkgname from %s' "${path}" + return + fi + + slock 10 "${_DEVTOOLS_PACMAN_CACHE_DIR}.lock" "Locking pacman database cache" + mapfile -t repos < <(pacman --config "${_DEVTOOLS_PACMAN_CONF_DIR}/multilib.conf" \ + --dbpath "${_DEVTOOLS_PACMAN_CACHE_DIR}" \ + -S \ + --print \ + --print-format '%n %r' \ + "${pkgnames[0]}" | grep -E "^${pkgnames[0]} " | awk '{print $2}' + ) + lock_close 10 + + printf "%s" "${repos[0]}" +} diff --git a/src/lib/valid-repos.sh b/src/lib/valid-repos.sh new file mode 100644 index 0000000..14f90ce --- /dev/null +++ b/src/lib/valid-repos.sh @@ -0,0 +1,22 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later +: + +# shellcheck disable=2034 +_repos=( + core core-staging core-testing + extra extra-staging extra-testing + multilib multilib-staging multilib-testing + gnome-unstable + kde-unstable +) + +# shellcheck disable=2034 +_build_repos=( + core-staging core-testing + extra extra-staging extra-testing + multilib multilib-staging multilib-testing + gnome-unstable + kde-unstable +) diff --git a/src/lib/valid-tags.sh b/src/lib/valid-tags.sh new file mode 100644 index 0000000..5382c5c --- /dev/null +++ b/src/lib/valid-tags.sh @@ -0,0 +1,26 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later +: + +# shellcheck disable=2034 +_arch=( + i686 + x86_64 + any +) + +# shellcheck disable=2034 +_tags=( + core-x86_64 core-any + core-staging-x86_64 core-staging-any + core-testing-x86_64 core-testing-any + extra-x86_64 extra-any + extra-staging-x86_64 extra-staging-any + extra-testing-x86_64 extra-testing-any + multilib-x86_64 + multilib-testing-x86_64 + multilib-staging-x86_64 + kde-unstable-x86_64 kde-unstable-any + gnome-unstable-x86_64 gnome-unstable-any +) diff --git a/src/lib/version/version.sh b/src/lib/version/version.sh new file mode 100644 index 0000000..d00a460 --- /dev/null +++ b/src/lib/version/version.sh @@ -0,0 +1,47 @@ +#!/hint/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +[[ -z ${DEVTOOLS_INCLUDE_VERSION_SH:-} ]] || return 0 +DEVTOOLS_INCLUDE_VERSION_SH=1 + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} + +source /usr/share/makepkg/util/message.sh + +set -e + + +pkgctl_version_usage() { + local -r COMMAND=${_DEVTOOLS_COMMAND:-${BASH_SOURCE[0]##*/}} + cat <<- _EOF_ + Usage: ${COMMAND} [OPTIONS] + + Shows the current version information of pkgctl + + OPTIONS + -h, --help Show this help text +_EOF_ +} + +pkgctl_version_print() { + cat <<- _EOF_ + pkgctl @buildtoolver@ +_EOF_ +} + +pkgctl_version() { + while (( $# )); do + case $1 in + -h|--help) + pkgctl_version_usage + exit 0 + ;; + *) + die "invalid argument: %s" "$1" + ;; + esac + done + + pkgctl_version_print +} diff --git a/src/makechrootpkg.in b/src/makechrootpkg.in index e56e912..aac7a0b 100644 --- a/src/makechrootpkg.in +++ b/src/makechrootpkg.in @@ -2,8 +2,13 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) -m4_include(lib/archroot.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} + +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/archroot.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh + source /usr/share/makepkg/util/config.sh @@ -24,6 +29,7 @@ clean_first=0 run_namcap=0 run_checkpkg=0 temp_chroot=0 +tmp_opts="nosuid,nodev,size=50%,nr_inodes=2m" bindmounts_ro=() bindmounts_rw=() @@ -304,7 +310,7 @@ done [[ -n $makepkg_user && -z $(id -u "$makepkg_user") ]] && die 'Invalid makepkg user.' makepkg_user=${makepkg_user:-${SUDO_USER:-$USER}} -check_root SOURCE_DATE_EPOCH,BUILDTOOL,BUILDTOOLVER,GNUPGHOME,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,MAKEFLAGS,PACKAGER +check_root SOURCE_DATE_EPOCH,BUILDTOOL,BUILDTOOLVER,GNUPGHOME,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,MAKEFLAGS,PACKAGER "${BASH_SOURCE[0]}" "$@" # Canonicalize chrootdir, getting rid of trailing / chrootdir=$(readlink -e "$passeddir") @@ -375,6 +381,7 @@ prepare_chroot if arch-nspawn "$copydir" \ --bind="${PWD//:/\\:}:/startdir" \ --bind="${SRCDEST//:/\\:}:/srcdest" \ + --tmpfs="/tmp:${tmp_opts}" \ "${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \ /chrootbuild "${makepkg_args[@]}" then diff --git a/src/makerepropkg.in b/src/makerepropkg.in index c58a923..398d4af 100644 --- a/src/makerepropkg.in +++ b/src/makerepropkg.in @@ -6,8 +6,12 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) -m4_include(lib/archroot.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/archroot.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh + source /usr/share/makepkg/util/config.sh source /usr/share/makepkg/util/message.sh @@ -93,7 +97,9 @@ get_makepkg_conf() { return 1 fi msg2 "using makepkg.conf from ${fname}" - bsdtar xOqf "${buildtool_file/file:\/\//}" "usr/share/devtools/makepkg-${arch}.conf" > "${makepkg_conf}" + if ! bsdtar xOqf "${buildtool_file/file:\/\//}" "usr/share/devtools/makepkg.conf.d/${arch}.conf" > "${makepkg_conf}"; then + bsdtar xOqf "${buildtool_file/file:\/\//}" "usr/share/devtools/makepkg-${arch}.conf" > "${makepkg_conf}" + fi return 0 } @@ -119,6 +125,9 @@ OPTIONS __EOF__ } +# save all args for check_root +orig_args=("$@") + while getopts 'dM:c:l:h' arg; do case "$arg" in d) diffoscope=1 ;; @@ -131,7 +140,7 @@ while getopts 'dM:c:l:h' arg; do done shift $((OPTIND - 1)) -check_root +check_root "" "${BASH_SOURCE[0]}" "${orig_args[@]}" [[ -f PKGBUILD ]] || { error "No PKGBUILD in current directory."; exit 1; } @@ -224,7 +233,7 @@ elif [[ "${BUILDTOOL}" = devtools ]] && get_makepkg_conf "${BUILDTOOL}-${BUILDTO # fallback to current makepkg.conf else warning "Unknown buildtool (${BUILDTOOL}-${BUILDTOOLVER}), using fallback" - makepkg_conf=@pkgdatadir@/makepkg-${CARCH}.conf + makepkg_conf=@pkgdatadir@/makepkg.conf.d/${CARCH}.conf fi printf '%s\n' "${allpkgfiles[@]}" | mkarchroot -M "${makepkg_conf}" -U "${archroot_args[@]}" "${namespace}/root" - || exit 1 diff --git a/src/mkarchroot.in b/src/mkarchroot.in index 5fac505..2abe001 100644 --- a/src/mkarchroot.in +++ b/src/mkarchroot.in @@ -2,8 +2,12 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) -m4_include(lib/archroot.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/archroot.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/archroot.sh + # umask might have been changed in /etc/profile # ensure that sane default is set again @@ -28,6 +32,9 @@ usage() { exit 1 } +# save all args for check_root +orig_args=("$@") + while getopts 'hUC:M:c:f:s' arg; do case "$arg" in U) umode=U ;; @@ -48,7 +55,7 @@ shift $((OPTIND - 1)) (( $# < 2 )) && die 'You must specify a directory and one or more packages.' -check_root +check_root "" "${BASH_SOURCE[0]}" "${orig_args[@]}" working_dir="$(readlink -f "$1")" shift 1 diff --git a/src/offload-build.in b/src/offload-build.in index 9e9d71e..027bad3 100644 --- a/src/offload-build.in +++ b/src/offload-build.in @@ -102,9 +102,9 @@ mapfile -t files < <( printf "\t%s\n" "$temp"/* } >&2 && makepkg_user_config="${XDG_CONFIG_HOME:-$HOME/.config}/pacman/makepkg.conf" && - makepkg_config="/usr/share/devtools/makepkg-'"${arch}"'.conf" && - if [[ -f /usr/share/devtools/makepkg-'"${repo}"'-'"${arch}"'.conf ]]; then - makepkg_config="/usr/share/devtools/makepkg-'"${repo}"'-'"${arch}"'.conf" + makepkg_config="/usr/share/devtools/makepkg.conf.d/'"${arch}"'.conf" && + if [[ -f /usr/share/devtools/makepkg.conf.d/'"${repo}"'-'"${arch}"'.conf ]]; then + makepkg_config="/usr/share/devtools/makepkg.conf.d/'"${repo}"'-'"${arch}"'.conf" fi && makepkg --config <(cat "${makepkg_user_config}" "${makepkg_config}" 2>/dev/null) --packagelist && printf "%s\n" "${temp}/PKGBUILD" diff --git a/src/pkgctl.in b/src/pkgctl.in new file mode 100644 index 0000000..1797b32 --- /dev/null +++ b/src/pkgctl.in @@ -0,0 +1,109 @@ +#!/bin/bash +# +# SPDX-License-Identifier: GPL-3.0-or-later + +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/config.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/config.sh + +set -e + + +usage() { + local -r COMMAND=${BASH_SOURCE[0]##*/} + cat <<- _EOF_ + Usage: ${COMMAND} [COMMAND] [OPTIONS] + + Unified command-line frontend for devtools. + + COMMANDS + auth Authenticate with services like GitLab + build Build packages inside a clean chroot + db Pacman database modification for packge update, move etc + diff Compare package files using different modes + release Release step to commit, tag and upload build artifacts + repo Manage Git packaging repositories and their configuration + version Show pkgctl version information + + OPTIONS + -h, --help Show this help text +_EOF_ +} + +if (( $# < 1 )); then + usage + exit 1 +fi + +export _DEVTOOLS_COMMAND='pkgctl' + +load_devtools_config + +# command checking +while (( $# )); do + case $1 in + -h|--help) + usage + exit 0 + ;; + build) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/build/build.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/build/build.sh + pkgctl_build "$@" + exit 0 + ;; + repo) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/repo.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/repo.sh + pkgctl_repo "$@" + exit 0 + ;; + auth) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/auth.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/auth.sh + pkgctl_auth "$@" + exit 0 + ;; + db) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/auth.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/db.sh + pkgctl_db "$@" + exit 0 + ;; + diff) + _DEVTOOLS_COMMAND+=" $1" + shift + diffpkg "$@" + exit 0 + ;; + release) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/release.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/release.sh + pkgctl_release "$@" + exit 0 + ;; + version|--version|-V) + _DEVTOOLS_COMMAND+=" $1" + shift + # shellcheck source=src/lib/version/version.sh + source "${_DEVTOOLS_LIBRARY_DIR}"/lib/version/version.sh + pkgctl_version "$@" + exit 0 + ;; + *) + die "invalid command: %s" "$1" + ;; + esac +done diff --git a/src/rebuildpkgs.in b/src/rebuildpkgs.in deleted file mode 100644 index eddc17f..0000000 --- a/src/rebuildpkgs.in +++ /dev/null @@ -1,111 +0,0 @@ -#!/bin/bash -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This script rebuilds a list of packages in order -# and reports anything that fails -# -# Due to sudo usage, it is recommended to allow makechrootpkg -# to be run with NOPASSWD in your sudoers file -# -# FIXME -# Currently uses $(pwd)/rebuilds as the directory for rebuilding... -# TODO make this work for community too - -m4_include(lib/common.sh) - -if (( $# < 1 )); then - printf 'Usage: %s <chrootdir> <packages to rebuild>\n' "$(basename "${BASH_SOURCE[0]}")" - printf ' example: %s ~/chroot readline bash foo bar baz\n' "$(basename "${BASH_SOURCE[0]}")" - exit 1 -fi - -# Source makepkg.conf; fail if it is not found -if [[ -r '/etc/makepkg.conf' ]]; then - # shellcheck source=config/makepkg/x86_64.conf - source '/etc/makepkg.conf' -else - die '/etc/makepkg.conf not found!' -fi - -# Source user-specific makepkg.conf overrides -if [[ -r "${XDG_CONFIG_HOME:-$HOME/.config}/pacman/makepkg.conf" ]]; then - # shellcheck source=/dev/null - source "${XDG_CONFIG_HOME:-$HOME/.config}/pacman/makepkg.conf" -elif [[ -r "$HOME/.makepkg.conf" ]]; then - # shellcheck source=/dev/null - source "$HOME/.makepkg.conf" -fi - -bump_pkgrel() { - # Get the current pkgrel from SVN and update the working copy with it - # This prevents us from incrementing out of control :) - pbuild='.svn/text-base/PKGBUILD.svn-base' - oldrel=$(grep 'pkgrel=' $pbuild | cut -d= -f2) - - #remove decimals - rel=${oldrel%%.*} - - newrel=$((rel + 1)) - - sed -i "s/pkgrel=$oldrel/pkgrel=$newrel/" PKGBUILD -} - -pkg_from_pkgbuild() { - # we want the sourcing to be done in a subshell so we don't pollute our current namespace - export CARCH PKGEXT - # shellcheck source=contrib/makepkg/PKGBUILD.proto - (source PKGBUILD; echo "$pkgname-$pkgver-$pkgrel-$CARCH$PKGEXT") -} - -chrootdir="$1"; shift -pkgs=("$@") - -SVNPATH='svn+ssh://repos.archlinux.org/srv/repos/svn-packages/svn' - -msg "Work will be done in %s" "$(pwd)/rebuilds" - -REBUILD_ROOT="$(pwd)/rebuilds" -mkdir -p "$REBUILD_ROOT" -cd "$REBUILD_ROOT" - -/usr/bin/svn co -N $SVNPATH - -FAILED="" -for pkg in "${pkgs[@]}"; do - cd "$REBUILD_ROOT/svn-packages" - - msg2 "Building '%s'" "$pkg" - /usr/bin/svn update "$pkg" - if [[ ! -d "$pkg/trunk" ]]; then - FAILED="$FAILED $pkg" - warning "%s does not exist in SVN" "$pkg" - continue - fi - cd "$pkg/trunk/" - - bump_pkgrel - - if ! sudo makechrootpkg -u -d -r "$chrootdir" -- --noconfirm; then - FAILED="$FAILED $pkg" - error "%s Failed!" "$pkg" - else - pkgfile=$(pkg_from_pkgbuild) - if [[ -e $pkgfile ]]; then - msg2 "%s Complete" "$pkg" - else - FAILED="$FAILED $pkg" - error "%s Failed, no package built!" "$pkg" - fi - fi -done - -cd "$REBUILD_ROOT" -if [[ -n $FAILED ]]; then - msg 'Packages failed:' - for pkg in $FAILED; do - msg2 "%s" "$pkg" - done -fi - -msg 'SVN pkgbumps in svn-packages/ - commit when ready' diff --git a/src/sogrep.in b/src/sogrep.in index d1ca1a1..0ee05cc 100644 --- a/src/sogrep.in +++ b/src/sogrep.in @@ -6,13 +6,17 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -m4_include(lib/common.sh) +_DEVTOOLS_LIBRARY_DIR=${_DEVTOOLS_LIBRARY_DIR:-@pkgdatadir@} +# shellcheck source=src/lib/common.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/common.sh +# shellcheck source=src/lib/valid-repos.sh +source "${_DEVTOOLS_LIBRARY_DIR}"/lib/valid-repos.sh + # globals -: ${SOLINKS_MIRROR:="https://mirror.pkgbuild.com"} +fallback_mirror='https://geo.mirror.pkgbuild.com' : ${SOCACHE_DIR:="${XDG_CACHE_HOME:-${HOME}/.cache}/sogrep"} -m4_include(lib/valid-repos.sh) arches=('x86_64') # options @@ -23,11 +27,16 @@ source /usr/share/makepkg/util/parseopts.sh source /usr/share/makepkg/util/util.sh recache() { - local repo arch verbosity=-s + local repo arch fallback_linksdburl linksdburl mirror verbosity=-s (( VERBOSE )) && verbosity=--progress-bar for repo in "${_repos[@]}"; do + if [[ -n "$SOLINKS_MIRROR" ]]; then + mirror="$SOLINKS_MIRROR" + elif ! mirror="$(set -o pipefail; pacman-conf --repo "$repo" Server 2>/dev/null | head -n1)"; then + mirror="$fallback_mirror" + fi for arch in "${arches[@]}"; do # delete extracted tarballs from previous sogrep versions rm -rf "${SOCACHE_DIR}/${arch}/${repo}" @@ -36,8 +45,20 @@ recache() { local dbpath=${SOCACHE_DIR}/${arch}/${repo}.links.tar.gz mkdir -p "${dbpath%/*}" (( VERBOSE )) && echo "Fetching ${repo}.links.tar.gz..." - if ! curl -fLR "${verbosity}" -o "${dbpath}" -z "${dbpath}" \ - "${SOLINKS_MIRROR}/${repo}/os/${arch}/${repo}.links.tar.gz"; then + + if [[ "$mirror" == *"/${repo}/os/${arch}" ]]; then + linksdburl="${mirror}/${repo}.links.tar.gz" + else + linksdburl="${mirror}/${repo}/os/${arch}/${repo}.links.tar.gz" + fi + fallback_linksdburl="${fallback_mirror}/${repo}/os/${arch}/${repo}.links.tar.gz" + + if curl -fLR "${verbosity}" -o "${dbpath}" -z "${dbpath}" "$linksdburl"; then + : + elif [[ "$linksdburl" != "$fallback_linksdburl" ]] \ + && curl -fLR "${verbosity}" -o "${dbpath}" -z "${dbpath}" "$fallback_linksdburl"; then + : + else echo "error: failed to download links database for repo ${repo}" exit 1 fi |