#!/usr/bin/env bash # librefetch # # Copyright (C) 2013-2018 Luke Shumaker # # For just the create_signature() function: # Copyright (C) 2006-2013 Pacman Development Team # Copyright (C) 2002-2006 Judd Vinet # Copyright (C) 2005 Aurelien Foret # Copyright (C) 2006 Miklos Vajna # Copyright (C) 2005 Christian Hamar # Copyright (C) 2006 Alex Smith # Copyright (C) 2006 Andras Voroskoi # # License: GNU GPLv3+ # # This file is part of LibreFetch. # # LibreFetch is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # LibreFetch is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LibreFetch. If not, see . # create_signature() is taken from pacman:makepkg, which is GPLv2+, # so we take the '+' to combine it with our GPLv3+. source "$(librelib conf)" source "$(librelib messages)" setup_traps trap 'rm -f -- "${tmpfiles[@]}"; rm -rf -- "${tmpdirs[@]}"' EXIT tmpfiles=() tmpdirs=() cmd=${0##*/} usage() { print "Usage: %s [OPTIONS] SOURCE_URL [OUTPUT_FILE]" "$cmd" print "Usage: %s -[g|S|M|h]" "$cmd" print "Downloads or creates a liberated source tarball." echo prose "The default mode is to create OUTPUT_FILE, first by trying download mode, then create mode." echo prose "If OUTPUT_FILE isn't specified, it defaults to the non-directory part of SOURCE_URL, in the current directory." echo prose "Unless '-C' is specified, if SOURCE_URL does not begin with a configured mirror, create mode is inhibited." echo prose "In download mode, it simply tries to download SOURCE_URL. At the beginning of a URL, 'libre://' expands to the first configured mirror." echo prose "In create mode, it either looks at a build script and uses that to create the source tarball, or it uses GPG to create a signature (if OUTPUT_FILE ends with \`.sig\` or \`.sig.part\`). If it is using GPG to create a signature, but the file which it is trying to sign doesn't exist yet, it recurses on itself to first create that file. SOURCE_URL is ignored, except that it is used to set the default value of OUTPUT_FILE, and that it may be used when recursing." echo prose "The default build script is 'PKGBUILD', or 'SRCBUILD' if it exists." echo prose "Other options, if they are valid \`makepkg\` options, are passed straight to makepkg." echo print "Example usage:" print ' $ %s https://repo.parabola.nu/other/mypackage/mypackage-1.0.tar.gz' "$cmd" echo flag 'Options (behavior):' \ "-C" "Force create mode (don't download)" \ "-D" "Force download mode (don't create)" \ "-p <$(_ FILE)>" "Use an alternate build script (instead of 'PKGBUILD'). If an SRCBUILD exists in the same directory, it is used instead" flag 'Options (reports):' \ "-g, --geninteg" "Generate integrity checks for source files" \ "-S, --srcbuild" "Print the effective build script (SRCBUILD)" \ "-M, --makepkg" "Generate and print the location of the effective makepkg script" \ "-h, --help" "Show this message" } main() { BUILDFILE="$(realpath -Lm PKGBUILD)" makepkg_opts=() extra_opts=() mode=download-create if ! parse_options "$@"; then usage >&2 exit $EXIT_INVALIDARGUMENT fi doit } doit() { # Mode: help ########################################################### if [[ $mode =~ help ]]; then usage exit $EXIT_SUCCESS fi ######################################################################## makepkg="$(modified_makepkg)" # Mode: makepkg ######################################################## if [[ $mode =~ makepkg ]]; then printf '%s\n' "$makepkg" exit $EXIT_SUCCESS else tmpdirs+=("${makepkg%/*}") fi ######################################################################## local BUILDFILEDIR="${BUILDFILE%/*}" if [[ -f "${BUILDFILEDIR}/SRCBUILD" ]]; then BUILDFILE="${BUILDFILEDIR}/SRCBUILD" fi if [[ ! -f "$BUILDFILE" ]]; then error "%s does not exist." "$BUILDFILE" exit $EXIT_FAILURE fi case "$BUILDFILE" in */SRCBUILD) srcbuild="$(modified_srcbuild "$BUILDFILE")";; *) srcbuild="$(modified_pkgbuild "$BUILDFILE")";; esac tmpfiles+=("$srcbuild") # Mode: checksums ###################################################### if [[ $mode =~ checksums ]]; then "$makepkg" "${makepkg_opts[@]}" -g -p "$srcbuild" | case ${BUILDFILE##*/} in PKGBUILD) sed -e 's/^[a-z]/mk&/' -e 's/^\s/ &/';; SRCBUILD) cat;; esac exit $EXIT_SUCCESS fi # Mode: srcbuild ####################################################### if [[ $mode =~ srcbuild ]]; then cat "$srcbuild" exit $EXIT_SUCCESS fi ######################################################################## local src="${extra_opts[0]}" local dst="${extra_opts[1]:-${src##*/}}" # Don't canonicalize $src unless mode =~ download, and we've validated # that $MIRRORS is configured. # Canonicalize $dst dst="$(realpath -Lm -- "$dst")" # Mode: download ####################################################### if [[ $mode =~ download ]]; then load_conf librefetch.conf MIRRORS DOWNLOADER || exit # Canonicalize $src if [[ "$src" == libre://* ]]; then src="${MIRRORS[0]}/${src#libre://}" fi # check to see if $src is a candidate for create mode local inmirror=false; local mirror for mirror in "${MIRRORS[@]}"; do if [[ "$src" == "$mirror"* ]]; then inmirror=true break fi done if ! $inmirror; then # inhibit create mode=download fi local dlcmd="${DOWNLOADER}" [[ $dlcmd = *%u* ]] || dlcmd="$dlcmd %u" dlcmd="${dlcmd//\%o/\"\$dst\"}" dlcmd="${dlcmd//\%u/\"\$src\"}" #DBG "librefetch::doit() mode==download dlcmd=$dlcmd" if { eval "$dlcmd"; } >&2; then exit $EXIT_SUCCESS fi #DBG "librefetch::doit() we did not exit success inmirror=$inmirror" fi # Mode: create ######################################################### # # This is where the 'mksource' magic is initiated. # The recursive `makepkg` invokation processes a modified PKGBUILD (per PKGBUILD_APPEND). if [[ $mode =~ create ]]; then local base_dst=${dst%.part} local suffix=${dst#"$base_dst"} local src_missing_msg="Libre source not found. Attempting to create it from upstream sources." local done_msg="Libre source created successfully" DBG "librefetch::doit(create) dst=$dst base_dst=${base_dst} suffix=$suffix" ; # DBG "PKGBUILD=" ; cat $srcbuild ; if [[ $base_dst == *.sig ]]; then # recurse to create the libre source-ball, if it does not yet exist # the libre source-ball signature is deferred to librerelease if ! [[ -e ${base_dst%.sig} ]]; then extra_opts=("${src%.sig}" "${base_dst%.sig}") msg2 "${src_missing_msg}" doit || exit fi DBG "librefetch::doit(create) IS_SIG" # set -x # FIXME: there is a note about this signature in usage() # create_signature "${base_dst%.sig}" || exit if [[ -n $suffix ]]; then mv -f "$base_dst" "$dst" fi else export PKGDEST=${dst%/*} export pkg_file=$dst DBG "librefetch::doit(create) PKGEXT=$PKGEXT cmd=\"$makepkg\" \"${makepkg_opts[@]}\" -p \"$srcbuild\"" cd "$BUILDFILEDIR" msg2 "${src_missing_msg}" "$makepkg" "${makepkg_opts[@]}" -p "$srcbuild" >&2 && msg2 "${done_msg}" || exit fi fi DBG "librefetch::doit(create) OUT" } # sets the variables BUILDFILE, makepkg_opts, extra_opts, mode parse_options() { declare -i ret=$EXIT_SUCCESS local {shrt,long}{1,2} # makepkg options local makepkg_orig makepkg_orig="$(which makepkg)" shrt1=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +-(.)(,| [^<]).*/\1/p')) shrt2=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +-(.) <.*/\1/p')) long1=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn -e 's/^ +(-., )?--(\S*) [^<].*/\2/p')) long2=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +--(\S*) <.*/\1/p')) # librefetch options shrt1+=(C D g S M h) shrt2+=(p) long1+=(geninteg srcbuild makepkg help) long2+=() # Feed the options through getopt (sanitize them) local shrt long args shrt="$({ printf '%s\0' "${shrt1[@]}"; printf '%s:\0' "${shrt2[@]}"; } | sort -zu | xargs -0 printf '%s')" long="$({ printf '%s\0' "${long1[@]}"; printf '%s:\0' "${long2[@]}"; } | sort -zu | xargs -0 printf '%s,')" args="$(getopt -n "$cmd" -o "$shrt" -l "${long%,}" -- "$@")" || ret=$EXIT_INVALIDARGUMENT eval "set -- $args" unset shrt long args # Parse the options. local opt optarg have_optarg while [[ $# -gt 0 ]]; do opt=$1; shift have_optarg=false if { [[ $opt == --?* ]] && in_array "${opt#--}" "${long2[@]}"; } \ || { [[ $opt == -? ]] && in_array "${opt#-}" "${shrt2[@]}"; } then optarg=$1; shift have_optarg=true fi case "$opt" in -C) mode=create;; -D) mode=download;; -g|--geninteg) mode=checksums;; -S|--srcbuild) mode=srcbuild;; -M|--makepkg) mode=makepkg;; -p) BUILDFILE="$(realpath -Lm -- "$optarg")";; -h|--help) mode=help;; --) break;; *) makepkg_opts+=("$opt") if $have_optarg; then makepkg_opts+=("$optarg"); fi ;; esac done extra_opts+=("$@") # check the number of extra_opts case "$mode" in help) # don't worry about it :;; checksums|srcbuild|makepkg) # don't take any extra arguments if [[ ${#extra_opts[@]} != 0 ]]; then print "%s: found extra non-flag arguments: %s" "$cmd" "${extra_opts[*]}" >&2 ret=$EXIT_INVALIDARGUMENT fi ;; *download*|*create*) # take 1 or 2 extra arguments if [[ ${#extra_opts[@]} != 1 ]] && [[ ${#extra_opts[@]} != 2 ]]; then print "%s: %d non-flag arguments found, expected 1 or 2: %s" "$cmd" ${#extra_opts[@]} >&2 ret=$EXIT_INVALIDARGUMENT fi ;; esac return $ret } # Modify makepkg ############################################################### modified_makepkg() { local dir dir="$(mktemp --tmpdir --directory "${cmd}.XXXXXXXXXXX.makepkg")" make -s -f "$(librelib librefetchdir/Makefile)" new="$dir" realpath -es "$dir/makepkg" } # Modify PKGBUILD ############################################################## # modified temporary mksource PKGBUILD over-rides readonly PKGBUILD_APPEND=' ## this is a temporary mksource PKGBUILD ## ## the modifications below, over-ride the abslibre PKGBUILD above to do mksource magic ## # ignore split packages - we want a single source-ball _is_split_pkg=$( [[ "$(declare -p pkgname 2> /dev/null)" =~ ^"declare -a " ]] ; echo $((!$?)) ) if (( _is_split_pkg )) then pkgname=( $( [[ -n "${pkgbase}" ]] && echo ${pkgbase} || echo ${pkgname} ) ) fi # replace *depends* arrays with mksource dependencies depends=() ; unset "depends_${CARCH}" ; checkdepends=() ; unset "checkdepends_${CARCH}" ; makedepends=("${mkdepends[@]}") ; unset "makedepends_${CARCH}" ; # replace source* arrays with mksource sources source=("${mksource[@]}") ; unset "source_${CARCH}" ; noextract=("${mknoextract[@]}") # replace *sums* arrays with mksource checksums declare algo for algo in ${known_hash_algos[*]} do eval "[[ -n \"\${mk${algo}sums[*]}\" ]] && \ ${algo}sums=(\${mk${algo}sums[*]}) || :" unset "${algo}sums_${CARCH}" done # cleanup any remaining, possibly conflicting data backup=() # set source-ball specific options (see packaging_options in the makepkg source) options=(!strip docs libtool staticlibs emptydirs !zipman !debug purge) PURGE_TARGETS=(.bzr/ .cvs/ .git/ .hg/ .svn/ .makepkg/) # over-ride build/packaging functions to do mksource magic _has_mksource=$( declare -f mksource > /dev/null ; echo $((!$?)) ) prepare() { : ; } build() { msg "Starting mksource()" ; (( has_mksource )) && mksource || : ; } check() { : ; } package() { cp -a "$srcdir"/*/ "$pkgdir/" ; } (( ! _is_split_pkg )) || for pkg in ${pkgname[*]} ; do unset package_${pkg} ; done ; ' modified_pkgbuild() { local pkgbuild=$1 local srcbuild srcbuild="$(mktemp "${pkgbuild%/*}/${cmd}.XXXXXXXXXXX.PKGBUILD.to.SRCBUILD")" printf '%s' "$PKGBUILD_APPEND" | cat "$pkgbuild" - > "$srcbuild" printf '%s\n' "$srcbuild" # printf "modified_pkgbuild() pkgbuild=${pkgbuild} ${pkgbuild%/*}\n" >&2 # printf "modified_pkgbuild() TMPDIR=$TMPDIR srcbuild_loc=%s\n" "$srcbuild" >&2 # ls -al ${pkgbuild%/*} } # Modify SRCBUILD ############################################################## modified_srcbuild() { local orig=$1 local new new="$(mktemp "${orig%/*}/${cmd}.XXXXXXXXXXX.SRCBUILD.to.SRCBUILD")" sed -e '/PKGDEST=/d' -e '/PKGEXT=/d' < "$orig" > "$new" printf '%s\n' "$new" } ################################################################################ create_signature() { local filename="$1" local gpg_cmd=( gpg --detach-sign --use-agent --no-armor ) local gpg_signing_msg="Signing libre source-ball..." local gpg_created_msg="Created signature file:" local gpg_remind_msg="Ensure that your GPG key is referenced in the PKGBUILD 'validpgpkeys' array." local gpg_failed_msg="Failed to sign the libre source-ball!" local gpg_sign_msg="If you can not sign it now on this machine, you can take it home, and run librerelease on it." local ret if [[ -n "${GPGKEY}" ]]; then gpg_cmd=( --local-user "${GPGKEY}" ) fi msg "${gpg_signing_msg}" ${gpg_cmd[@]} "${filename}" &> /dev/null ; ret=$? ; if (( ! ret )); then msg2 "%s %s." "${gpg_created_msg}" "${filename}.sig" ; plain "${gpg_remind_msg}" ; else error "${gpg_failed_msg}" ; plain "${gpg_sign_msg}" ; plain "${gpg_remind_msg}" ; fi return ${ret} } main "$@"