Bump S-nail v14.9.25 ("Lubimy Gorod"), 2024-06-27
[s-mailx.git] / mk / make-release.inc
blob6f4a6baed3dc6c8a82808598fdcc1a2eb255a55d
1 #@ Include file for the make-release.sh generic release builder.
2 #@ It also needs three hooks: update_stable_hook() and update_release_hook(),
3 #@ which need to "git add" what they have modified, and test_release_hook(),
4 #@ (non-"grappa") called with the release tag name as an argument after the
5 #@ release commit: if it returns a non-0 status the release process is aborted.
6 #@ The "grappa" mode needs a current_version() hook, which has to set $VERSION
7 #@ to the current program version, expected as MAJOR.MINOR.UPDATE[-whatever]
9 # Program stuff
10 : ${awk:=awk}
11 : ${cat:=cat}
12 : ${cmp:=cmp}
13 : ${date:=date}
14 : ${git:=git}
15 : ${grep:=grep}
16 : ${make:=make}
17 : ${mv:=mv}
18 : ${SHELL:=sh}
19 : ${sed:=sed}
21 # Non-"grappa" only
22 : ${gzip:=gzip}
23 : ${openssl:=openssl}
24 : ${gpg:=gpg}
25 : ${rm:=rm}
26 : ${roff:=s-roff} # optional (command(1) tested)
27 : ${sftp:=sftp}
28 : ${tar:=tar}
29 : ${xz:=xz}
31 ##  --  >8  --  8<  --  ##
33 : ${PROGRAM:?"Need \$PROGRAM"}
34 : ${UPROGRAM:?"Need \$UPROGRAM"}
35 # For announcement only.
36 : ${MANUAL:?"May need \$MANUAL for announcement references"}
38 # When we upload balls only.
39 : ${UPLOAD:?"Need \$UPLOAD URL for scp(1)"}
41 # For announcement mail only.
42 : ${MAILX:=mailx}
43 : ${ACCOUNT:?"May need mailx(1) -A \$ACCOUNT"}
44 : ${MAILTO:?"May need \$MAILTO for announcement"}
45 : ${MAILBCC:?"May need \$MAILBCC for announcement"}
47 ##  --  >8  --  8<  --  ##
49 ORIG_LC_ALL=${LC_ALL} LC_ALL=C
50 export LC_ALL
52 DATE_MAN=`${date} -u +'%B %d, %Y'`
53 DATE_ISO=`${date} -u +%Y-%m-%d`
55 yesno() {
56    while [ 1 ]; do
57       [ ${#} -gt 0 ] && printf '%s ' "${@}"
58       printf '[y/n] '
59       read i
60       case ${i} in
61       [Yy]*) return 0;;
62       [Nn]*) return 1;;
63       *) ;;
64       esac
65    done
68 ref_status() {
69    headref="`${git} rev-parse --verify HEAD`"
70    brref=
71    for i in `${git} rev-parse --branches=stable master^{commit} \
72          2>/dev/null`; do
73       if [ ${headref} = ${i} ]; then
74          brref=${headref}
75          break
76       fi
77    done
78    if [ -z "${brref}" ]; then
79       echo >&2 'Not on the [master] or a [stable/*] branch'
80       [ -z "${grappa}" ] && exit 1
81       if yesno 'Are you sure you want grappa from '${headref}'?'; then :; else
82          echo >&2 'Bailing out'
83          exit 3
84       fi
85    fi
86    if [ "`${git} status --porcelain --ignored |
87          ${awk} 'BEGIN{no=0}{++no}END{print no}'`" -ne 0 ]; then
88       echo >&2 'Directory not clean, see git status --ignored'
89       exit 2
90    fi
91    #brname="`git branch | sed -e '/^* /b X' -e d -e :X -e 's/^* //'`"
92    brname=`${git} symbolic-ref --short HEAD`
95 release_version() {
96    vmaj=`{ echo ${VERSION}; } | ${sed} -e 's/^\([^.]\{1,\}\).*/\1/'`
97    vmin=`{ echo ${VERSION}; } |
98       ${sed} -e 's/^[^.]\{1,\}\.\([^.]\{1,\}\).*/\1/'`
99    [ ${vmin} = ${VERSION} ] && VERSION=${VERSION}.0 vmin=0
100    vupd=`{ echo ${VERSION}; } |
101          ${sed} -e 's/^[^.]\{1,\}\.[^.]\{1,\}\.\([^.-]\{1,\}\).*/\1/'`
102    [ ${vupd} = ${VERSION} ] && VERSION=${VERSION}.0 vupd=0
103    REL=${VERSION}
104    export VERSION
105    if yesno 'Is '${PROGRAM}' <v'${REL}'> correct?'; then :; else
106       echo >&2 'Bailing out'
107       exit 3
108    fi
111 release_brcheck() {
112    stblbrname=stable/v${vmaj}.${vmin} need_stblbrname=
113    brref=`${git} rev-parse --verify ${stblbrname} 2>/dev/null`
114    if [ -z "${brref}" ]; then
115       if yesno 'Create new branch '"${stblbrname}"' after release tag'; then
116          need_stblbrname=1
117       fi
118    elif [ ${brref} != ${headref} ] || [ ${brname} != ${stblbrname} ]; then
119       echo >&2 "For ${REL} we should be on ${stblbrname}, not ${brname}"
120       echo >&2 'Bailing out'
121       exit 4
122    fi
124    relbrname=release/v${VERSION}
125    brref=`${git} rev-parse --verify ${relbrname} 2>/dev/null`
126    if [ -z "${brref}" ]; then :; else
127       echo >&2 "The ${relbrname} already exists"
128       echo >&2 'Bailing out'
129       exit 5
130    fi
133 release_symname() {
134    RELSYM=
135    stblmsg= relmsg=
136    if yesno 'Shall '${PROGRAM}' v'${REL}' have a symbolic name?'; then
137       printf '  ..and it shall be known as: '
138       read RELSYM
139       if yesno 'Is '"${RELSYM}"' correct?'; then :; else
140          echo >&2 'Bailing out'
141          exit 3
142       fi
143       stblmsg="Bump ${UPROGRAM} v${REL} (\"${RELSYM}\"), ${DATE_ISO}"
144       relmsg="Bump ${UPROGRAM} v${REL}.ar (\"${RELSYM}\"), ${DATE_ISO}"
145       RELSYM=" (\"${RELSYM}\")"
146    else
147       stblmsg="Bump ${UPROGRAM} v${REL}, ${DATE_ISO}"
148       relmsg="Bump ${UPROGRAM} v${REL}.ar, ${DATE_ISO}"
149    fi
152 update_stable_branch() {
153    LC_ALL=${ORIG_LC_ALL} ${git} commit -S -n -m "${stblmsg}"
154    LC_ALL=${ORIG_LC_ALL} ${git} tag -s -f -m "${stblmsg}" v${REL}
156    if [ -n "${need_stblbrname}" ]; then
157       ${git} checkout -b ${stblbrname}
158    fi
159    # Normally done in post-commit hook, but not once initially created
160    if yesno 'Shall i update stable/latest "symlink"?'; then
161       ${git} update-ref refs/heads/stable/latest ${stblbrname}
162    fi
163    if yesno 'Shall i update stable/stable "symlink"?'; then
164       ${git} update-ref refs/heads/stable/stable ${stblbrname}
165    fi
168 create_release_branch() {
169    if yesno 'Create release/ branch?'; then
170       ${git} checkout -b ${relbrname}
172       echo 'Updating files: calling update_release_hook'
173       update_release_hook
175       LC_ALL=${ORIG_LC_ALL} ${git} commit -S -n -m "${relmsg}"
176       LC_ALL=${ORIG_LC_ALL} ${git} tag -s -f -m "${relmsg}" v${REL}.ar
178       if yesno 'Shall i update release/latest "symlink"?'; then
179          ${git} update-ref refs/heads/release/latest ${relbrname}
180       fi
181       if yesno 'Shall i update release/stable "symlink"?'; then
182          ${git} update-ref refs/heads/release/stable ${relbrname}
183       fi
184    else
185       relbrname=${stblbrname}
186    fi
189 check_timeline_branch() {
190    if [ ${relbrname} != ${stblbrname} ] &&
191          `${git} rev-parse --verify timeline^{commit} >/dev/null 2>&1` &&
192          yesno 'Shall i update [timeline]?'; then
193       ${git} checkout timeline
194       ${git} rm -rf '*'
195       ${git} archive --format=tar "v${REL}.ar" | ${tar} -x -f -
196       ${git} add .
197       LC_ALL=${ORIG_LC_ALL} ${git} commit -S -n -m "${relmsg}"
198    fi
201 repo_push() {
202    [ ${relbrname} != ${stblbrname} ] && ${git} checkout ${stblbrname}
203    ${git} log --no-walk --decorate --oneline --branches --remotes
204    yesno 'Push git(1) repo?' && ${git} push
207 big_balls() {
208    if [ ${relbrname} != ${stblbrname} ] && yesno 'Create tarballs?'; then
209       bigballs=y
210       (
211       umask 0022
213       # Repack with standard tar(1) to avoid new-style headers
214       ${git} archive --format=tar --prefix="${PROGRAM}-${REL}/" v${REL}.ar |
215          ( cd "${TMPDIR}" && ${tar} -x -f - )
216       cd "${TMPDIR}"
218       # And use a portable format by default; ustar from 1988 is ok for us,
219       # but pax/posix from 2001 is even more forgiving and future aware
220       fmt=
221       ${tar} --format=pax --version >/dev/null 2>&1 && fmt=--format=pax
222       ${tar} -c ${fmt} -f "${PROGRAM}-${REL}.tar" "${PROGRAM}-${REL}"
223       < "${PROGRAM}-${REL}.tar" ${xz} -e -C sha256 > "${PROGRAM}-${REL}.tar.xz"
224       < "${PROGRAM}-${REL}.tar" ${gzip} -9 > "${PROGRAM}-${REL}.tar.gz"
225       ${rm} "${PROGRAM}-${REL}.tar"
227       printf '' > "${PROGRAM}-${REL}.cksum"
228       ${openssl} sha1 "${PROGRAM}-${REL}.tar.xz" >> "${PROGRAM}-${REL}.cksum"
229       ${openssl} sha256 "${PROGRAM}-${REL}.tar.xz" >> "${PROGRAM}-${REL}.cksum"
230       ${openssl} sha512 "${PROGRAM}-${REL}.tar.xz" >> "${PROGRAM}-${REL}.cksum"
231       ${openssl} sha1 "${PROGRAM}-${REL}.tar.gz" >> "${PROGRAM}-${REL}.cksum"
232       ${openssl} sha256 "${PROGRAM}-${REL}.tar.gz" >> "${PROGRAM}-${REL}.cksum"
233       ${openssl} sha512 "${PROGRAM}-${REL}.tar.gz" >> "${PROGRAM}-${REL}.cksum"
235       echo >> "${PROGRAM}-${REL}.cksum"
236       ${gpg} --detach-sign --armor "${PROGRAM}-${REL}.tar.xz"
237       ${cat} "${PROGRAM}-${REL}.tar.xz.asc" >> "${PROGRAM}-${REL}.cksum"
238       ${gpg} --detach-sign --armor "${PROGRAM}-${REL}.tar.gz"
239       ${cat} "${PROGRAM}-${REL}.tar.gz.asc" >> "${PROGRAM}-${REL}.cksum"
241       [ -f "${PROGRAM}".cat1 ] &&
242          < "${PROGRAM}".cat1 ${xz} -e -C sha256 > "${PROGRAM}-${REL}.cat1.xz"
243       [ -f "${PROGRAM}".xcat1 ] &&
244          < "${PROGRAM}".xcat1 ${xz} -e -C sha256 > "${PROGRAM}-${REL}.xcat1.xz"
245       )
246    else
247       bigballs=
248    fi
251 announcement_prepare() {
252    anntxt=
253    if yesno 'Prepare announcement?'; then :; else
254       return
255    fi
256    anntxt=y
258    if `${git} cat-file -e ${relbrname}:NEWS 2>/dev/null`; then
259       ${git} show ${relbrname}:NEWS > "${TMPDIR}/.${PROGRAM}-${REL}.news"
260    else
261       printf '' > "${TMPDIR}/.${PROGRAM}-${REL}.news"
262    fi
264    { echo "${relmsg}"; echo; } > "${TMPDIR}/${PROGRAM}-${REL}.txt"
265    if [ -f .git/make-release.txt ]; then
266       # For the checksums
267       if [ -n "${bigballs}" ] && [ -f "${TMPDIR}/${PROGRAM}-${REL}.cksum" ]
268       then
269          cks=`< "${TMPDIR}/${PROGRAM}-${REL}.cksum" \
270                ${sed} -e 's/ //' -e '/^$/,$d'`
271          < "${TMPDIR}/${PROGRAM}-${REL}.cksum" ${sed} '1,/^$/d' \
272             > "${TMPDIR}/.${PROGRAM}-${REL}.sigs"
273          < .git/make-release.txt ${awk} \
274                -v INS="${cks}" -v SIGS="${TMPDIR}/.${PROGRAM}-${REL}.sigs" \
275                -v NEWS="${TMPDIR}/.${PROGRAM}-${REL}.news" '
276             /-----CHECKSUMS-----/{
277                atop = split(INS, a)
278                fn = ""
279                for(i = 1; i <= atop; ++i){
280                   match(a[i], /(\(.+\))/)
281                   tfn = substr(a[i], RSTART + 1, RLENGTH - 2)
282                   tpre = substr(a[i], 1, RSTART - 1)
283                   tsuf = substr(a[i], RSTART + RLENGTH + 1)
284                   if(fn == "" || fn != tfn)
285                      printf "%s:\n", (fn = tfn)
286                   printf "  %6s %s\n", tpre, tsuf
287                }
288                next
289             }
290             /-----SIGNATURES-----/{
291                while((getline sl < SIGS) > 0)
292                   print sl
293                next
294             }
295             /-----NEWS-----/{
296                while((getline sl < NEWS) > 0)
297                   print sl
298                next
299             }
300             {print}
301          ' >> "${TMPDIR}/${PROGRAM}-${REL}.txt"
302          ${rm} -f "${TMPDIR}/.${PROGRAM}-${REL}.sigs"
303       else
304          < .git/make-release.txt ${awk} \
305                -v NEWS="${TMPDIR}/.${PROGRAM}-${REL}.news" '
306             /-----NEWS-----/{
307                while((getline sl < NEWS) > 0)
308                   print sl
309                next
310             }
311             {print}
312          ' >> "${TMPDIR}/${PROGRAM}-${REL}.txt"
313       fi
314    elif [ -f "${TMPDIR}/.${PROGRAM}-${REL}.news" ]; then
315       ${cat} "${TMPDIR}/.${PROGRAM}-${REL}.news" >> \
316          "${TMPDIR}/${PROGRAM}-${REL}.txt"
317    fi
319    ${rm} -f "${TMPDIR}/.${PROGRAM}-${REL}.news"
321    LC_ALL=${ORIG_LC_ALL} ${EDITOR} "${TMPDIR}/${PROGRAM}-${REL}.txt"
323    # HTML convert ready for S-Web42
324    APO=\'
325    < "${TMPDIR}/${PROGRAM}-${REL}.txt" ${awk} -v manual="${MANUAL}" '
326    BEGIN{
327       hot = 0
328       print "<?begin?><?mode icewatsm?><pre>"
329    }
330    function strips(){
331       gsub("&", "\\&amp;")
332       gsub("<", "\\&lt;")
333       gsub(">", "\\&gt;")
334    }
335    function urls(){
336       any = 0
337       res = ""
338       s = $0
340       while(match(s, /(\\?https?\??:\/\/[^ ]*)/)){
341          pre = substr(s, 1, RSTART - 1)
342          mat = substr(s, RSTART, RLENGTH)
343          s = substr(s, RSTART + RLENGTH)
344          if("\\" == substr(mat, 1, 1))
345             mat = substr(mat, 2)
346          else{
347             xt = 0
348             if(match(mat, /^https\?/))
349                mat = "https" substr(xt = mat, RSTART + 6)
350             if(match(mat, /sdaoden\.eu/))
351                mat = "<?lref" (xt ? "t " : " ") mat (xt ? "<>" xt : "") "?>"
352             else
353                mat = "<?href" (xt ? "t " : " ") mat (xt ? "<>" xt : "") "?>"
354          }
355          res = res pre mat
356          any = 1
357       }
358       if(any && length(s))
359          res = res s
360       $0 = any ? res : s
361    }
362    /^[  ]*s-.*-mode[    ]*$/{
363       exit 0
364    }
365    /^(NOTES|ChangeLog)/{
366       hot = 1
367       strips()
368       print
369       next
370    }
371    /^(Appendix|git\(1\) shortlog)/{
372       hot = -1
373       strips()
374       print
375       next
376    }
377    {
378       strips()
379       urls()
380       if(hot <= 0){
381          print
382          next
383       }
384       any = 0
385       res = ""
386       s = $0
387       # Create S-Web42 local references for the possible anchors:
388       #     *XY*# / $XY# / -XY# / `XY${APO}# / `~XY${APO}# / "XY"#
389       # (where the mdocmx(7) anchor follows the number sign).
390       # Ideally the anchors have been automatically expanded by
391       # make-news-anchors.sh before.
392       while(match(s,
393             /(^|\(|[    ]+)("[^"]+"|\*[^\*]+\*|`[^'${APO}']+'${APO}'|[-~][-#\/:_.0-9a-zA-Z]+|\$[_0-9a-zA-Z]+)#[0-9]+/))
394       {
395          pre = (RSTART > 1) ? substr(s, 1, RSTART - 1) : ""
396          mat = substr(s, RSTART, RLENGTH)
397          s = substr(s, RSTART + RLENGTH)
399          # Unfortunately groups are not supported
400          if(match(mat, /^(\(|[  ]+)/) != 0 && RLENGTH > 0){
401             pre = pre substr(mat, 1, RLENGTH)
402             mat = substr(mat, RSTART + RLENGTH)
403          }
405          match(mat, /#[0-9]+/)
406          targ = substr(mat, RSTART + 1, RLENGTH)
407          mat = substr(mat, 1, RSTART - 1)
408          res = res pre "<?lreft " manual "#" targ "<>" mat "?>"
409          any = 1
410       }
411       if(any && length(s))
412          res = res s
413       print any ? res : s
414    }
415    END{
416       print "</pre><?end?>"
417    }
418    ' > "${TMPDIR}/.${PROGRAM}-ann.html"
421 upload() (
422    if [ -n "${bigballs}" ] && yesno 'Upload archives'; then :; else
423       return
424    fi
425    cd "${TMPDIR}"
427    {
428       echo "-put ${PROGRAM}-${REL}.tar.xz"
429       echo "-rm ${PROGRAM}-latest.tar.xz"
430       echo "-ln -s ${PROGRAM}-${REL}.tar.xz ${PROGRAM}-latest.tar.xz"
432       echo "-put ${PROGRAM}-${REL}.tar.xz.asc"
433       echo "-rm ${PROGRAM}-latest.tar.xz.asc"
434       echo "-ln -s ${PROGRAM}-${REL}.tar.xz.asc ${PROGRAM}-latest.tar.xz.asc"
436       echo "-put ${PROGRAM}-${REL}.tar.gz"
437       echo "-rm ${PROGRAM}-latest.tar.gz"
438       echo "-ln -s ${PROGRAM}-${REL}.tar.gz ${PROGRAM}-latest.tar.gz"
440       echo "-put ${PROGRAM}-${REL}.tar.gz.asc"
441       echo "-rm ${PROGRAM}-latest.tar.gz.asc"
442       echo "-ln -s ${PROGRAM}-${REL}.tar.gz.asc ${PROGRAM}-latest.tar.gz.asc"
444       if [ -n "${anntxt}" ]; then
445          echo "-put ${PROGRAM}-${REL}.txt"
446          echo "-rm ${PROGRAM}-latest.txt"
447          echo "-ln -s ${PROGRAM}-${REL}.txt ${PROGRAM}-latest.txt"
448       fi
450       echo "-chmod 0644 ${PROGRAM}-${REL}.*"
451    } |
452    ${sftp} -b - ${UPLOAD}
455 announcement_send() {
456    if [ -n "${anntxt}" ] && yesno 'Send announcement mail?'; then
457       LC_ALL=${ORIG_LC_ALL} ${MAILX} -A ${ACCOUNT} \
458          -s "[ANN]ounce of ${UPROGRAM} v${REL}${RELSYM}" \
459          -q "${TMPDIR}/${PROGRAM}-${REL}.txt" \
460          -b ${MAILBCC} ${MAILTO}
461    fi
464 create_grappa_env() {
465    echo 'Updating files: calling update_release_hook'
466    update_release_hook
467    echo 'E allora io quasi quasi prendo il treno'
470 grappa=
471 if [ ${#} -ne 0 ]; then
472    if [ ${#} != 2 ] || [ "${1}" != grappa ] || [ -z "${2}" ]; then
473       echo >&2 'You have a hell of a lot to learn about Rock'"'"'n Roll'
474       exit 55
475    fi
476    grappa=${2}
479 ref_status
480 echo 'Preparing a release on commit '"${headref}"
481 if [ -z "${grappa}" ]; then
482    printf '  The HEAD is %s\nName of release tag: ' "${brname}"
483    read REL
484    VERSION=${REL}
485    release_version
486    release_brcheck
487    release_symname
488 else
489    echo 'Grappa to be brought from '"${brname}"' to '"${grappa}"
490    current_version
491    printf 'Program version is %s, packager release addition shall be: ' \
492       "${VERSION}"
493    read REL
494    VERSION="${VERSION}-${REL}"
495    release_version
497    i=
498    if ${git} rev-parse --verify ${grappa} >/dev/null 2>/dev/null; then :; else
499       i=-B
500    fi
501    ${git} checkout ${i} ${grappa}
502    ${git} rm -f '*'
503    ${git} archive --format=tar ${headref} | ${tar} -x -f -
504    ${git} add .
507 echo 'Updating files: calling update_stable_hook'
508 update_stable_hook
510 if [ -z "${grappa}" ]; then
511    update_stable_branch
512    create_release_branch
513    test_release_hook v${REL}.ar || exit 1
514    check_timeline_branch
515    repo_push
516    big_balls
517    announcement_prepare
518    upload
519    announcement_send
520 else
521    create_grappa_env
525 # Finally remove the temporary instances than ran this
526 ${rm} -f .git/make-release.*
527 echo 'Done'
528 exit
529 # s-sh-mode