summaryrefslogtreecommitdiff
path: root/src/librefetch/librefetch
blob: 6767a6ced56e380aa3ec9ce47dcef5f432299cc4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
#!/usr/bin/env bash
# librefetch
#
# Copyright (C) 2013-2018 Luke Shumaker <lukeshu@parabola.nu>
#
# For just the create_signature() function:
#   Copyright (C) 2006-2013 Pacman Development Team <pacman-dev@archlinux.org>
#   Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
#   Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
#   Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
#   Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
#   Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
#   Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
#
# 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 <http://www.gnu.org/licenses/>.

# 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\"}"

    if { eval "$dlcmd"; } >&2; then
      exit $EXIT_SUCCESS
    fi
  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"

    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
# 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

      cd "$BUILDFILEDIR"
      msg2 "${src_missing_msg}"
      "$makepkg" "${makepkg_opts[@]}" -p "$srcbuild" >&2 && msg2 "${done_msg}" || exit
    fi
  fi
}

# 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"
}


# 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 "$@"