• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/bin/bash
2#
3#		Creates and upload a git module tarball
4#
5# Note on portability:
6# This script is intended to run on any platform supported by X.Org.
7# Basically, it should be able to run in a Bourne shell.
8#
9#
10
11export LC_ALL=C
12
13#------------------------------------------------------------------------------
14#			Function: check_local_changes
15#------------------------------------------------------------------------------
16#
17check_local_changes() {
18    git diff --quiet HEAD > /dev/null 2>&1
19    if [ $? -ne 0 ]; then
20	echo ""
21	echo "Uncommitted changes found. Did you forget to commit? Aborting."
22	echo ""
23	echo "You can perform a 'git stash' to save your local changes and"
24	echo "a 'git stash apply' to recover them after the tarball release."
25	echo "Make sure to rebuild and run 'make distcheck' again."
26	echo ""
27	echo "Alternatively, you can clone the module in another directory"
28	echo "and run ./configure. No need to build if testing was finished."
29	echo ""
30	return 1
31    fi
32    return 0
33}
34
35#------------------------------------------------------------------------------
36#			Function: check_option_args
37#------------------------------------------------------------------------------
38#
39# perform sanity checks on cmdline args which require arguments
40# arguments:
41#   $1 - the option being examined
42#   $2 - the argument to the option
43# returns:
44#   if it returns, everything is good
45#   otherwise it exit's
46check_option_args() {
47    option=$1
48    arg=$2
49
50    # check for an argument
51    if [ x"$arg" = x ]; then
52	echo ""
53	echo "Error: the '$option' option is missing its required argument."
54	echo ""
55	usage
56	exit 1
57    fi
58
59    # does the argument look like an option?
60    echo $arg | $GREP "^-" > /dev/null
61    if [ $? -eq 0 ]; then
62	echo ""
63	echo "Error: the argument '$arg' of option '$option' looks like an option itself."
64	echo ""
65	usage
66	exit 1
67    fi
68}
69
70#------------------------------------------------------------------------------
71#			Function: check_modules_specification
72#------------------------------------------------------------------------------
73#
74check_modules_specification() {
75
76if [ x"$MODFILE" = x ]; then
77    if [ x"${INPUT_MODULES}" = x ]; then
78	echo ""
79	echo "Error: no modules specified (blank command line)."
80	usage
81	exit 1
82    fi
83fi
84
85}
86
87#------------------------------------------------------------------------------
88#			Function: generate_announce
89#------------------------------------------------------------------------------
90#
91generate_announce()
92{
93    cat <<RELEASE
94Subject: [ANNOUNCE] $pkg_name $pkg_version
95To: $list_to
96Cc: $list_cc
97
98`git log --no-merges "$tag_range" | git shortlog`
99
100git tag: $tag_name
101
102RELEASE
103
104    for tarball in $tarbz2 $targz $tarxz; do
105	cat <<RELEASE
106http://$host_current/$section_path/$tarball
107MD5:  `$MD5SUM $tarball`
108SHA1: `$SHA1SUM $tarball`
109SHA256: `$SHA256SUM $tarball`
110PGP:  http://${host_current}/${section_path}/${tarball}.sig
111
112RELEASE
113    done
114}
115
116#------------------------------------------------------------------------------
117#			Function: read_modfile
118#------------------------------------------------------------------------------
119#
120# Read the module names from the file and set a variable to hold them
121# This will be the same interface as cmd line supplied modules
122#
123read_modfile() {
124
125    if [ x"$MODFILE" != x ]; then
126	# Make sure the file is sane
127	if [ ! -r "$MODFILE" ]; then
128	    echo "Error: module file '$MODFILE' is not readable or does not exist."
129	    exit 1
130	fi
131	# read from input file, skipping blank and comment lines
132	while read line; do
133	    # skip blank lines
134	    if [ x"$line" = x ]; then
135		continue
136	    fi
137	    # skip comment lines
138	    if echo "$line" | $GREP -q "^#" ; then
139		continue;
140	    fi
141	    INPUT_MODULES="$INPUT_MODULES $line"
142	done <"$MODFILE"
143    fi
144    return 0
145}
146
147#------------------------------------------------------------------------------
148#			Function: print_epilog
149#------------------------------------------------------------------------------
150#
151print_epilog() {
152
153    epilog="========  Successful Completion"
154    if [ x"$NO_QUIT" != x ]; then
155	if [ x"$failed_modules" != x ]; then
156	    epilog="========  Partial Completion"
157	fi
158    elif [ x"$failed_modules" != x ]; then
159	epilog="========  Stopped on Error"
160    fi
161
162    echo ""
163    echo "$epilog `date`"
164
165    # Report about modules that failed for one reason or another
166    if [ x"$failed_modules" != x ]; then
167	echo "	List of failed modules:"
168	for mod in $failed_modules; do
169	    echo "	$mod"
170	done
171	echo "========"
172	echo ""
173    fi
174}
175
176#------------------------------------------------------------------------------
177#			Function: process_modules
178#------------------------------------------------------------------------------
179#
180# Loop through each module to release
181# Exit on error if --no-quit was not specified
182#
183process_modules() {
184    for MODULE_RPATH in ${INPUT_MODULES}; do
185	if ! process_module ; then
186	    echo "Error: processing module \"$MODULE_RPATH\" failed."
187	    failed_modules="$failed_modules $MODULE_RPATH"
188	    if [ x"$NO_QUIT" = x ]; then
189		print_epilog
190		exit 1
191	    fi
192	fi
193    done
194}
195
196#                       Function: sign_or_fail
197#------------------------------------------------------------------------------
198#
199# Sign the given file, if any
200# Output the name of the signature generated to stdout (all other output to
201# stderr)
202# Return 0 on success, 1 on fail
203#
204sign_or_fail() {
205    if [ -n "$1" ]; then
206	sig=$1.sig
207	rm -f $sig
208	$GPG -b $1 1>&2
209	if [ $? -ne 0 ]; then
210	    echo "Error: failed to sign $1." >&2
211	    return 1
212	fi
213	echo $sig
214    fi
215    return 0
216}
217
218#------------------------------------------------------------------------------
219#			Function: process_module
220#------------------------------------------------------------------------------
221# Code 'return 0' on success to process the next module
222# Code 'return 1' on error to process next module if invoked with --no-quit
223#
224process_module() {
225
226    top_src=`pwd`
227    echo ""
228    echo "========  Processing \"$top_src/$MODULE_RPATH\""
229
230    # This is the location where the script has been invoked
231    if [ ! -d $MODULE_RPATH ] ; then
232	echo "Error: $MODULE_RPATH cannot be found under $top_src."
233	return 1
234    fi
235
236    # Change directory to be in the git module
237    cd $MODULE_RPATH
238    if [ $? -ne 0 ]; then
239	echo "Error: failed to cd to $MODULE_RPATH."
240	return 1
241    fi
242
243    # ----- Now in the git module *root* directory ----- #
244
245    # Check that this is indeed a git module
246    if [ ! -d .git ]; then
247	echo "Error: there is no git module here: `pwd`"
248	return 1
249    fi
250
251    # Change directory to be in the git build directory (could be out-of-source)
252    # More than one can be found when distcheck has run and failed
253    configNum=`find . -name config.status -type f | wc -l | sed 's:^ *::'`
254    if [ $? -ne 0 ]; then
255	echo "Error: failed to locate config.status."
256	echo "Has the module been configured?"
257	return 1
258    fi
259    if [ x"$configNum" = x0 ]; then
260	echo "Error: failed to locate config.status, has the module been configured?"
261	return 1
262    fi
263    if [ x"$configNum" != x1 ]; then
264	echo "Error: more than one config.status file was found,"
265	echo "       clean-up previously failed attempts at distcheck"
266	return 1
267    fi
268    status_file=`find . -name config.status -type f`
269    if [ $? -ne 0 ]; then
270	echo "Error: failed to locate config.status."
271	echo "Has the module been configured?"
272	return 1
273    fi
274    build_dir=`dirname $status_file`
275    cd $build_dir
276    if [ $? -ne 0 ]; then
277	echo "Error: failed to cd to $MODULE_RPATH/$build_dir."
278	cd $top_src
279	return 1
280    fi
281
282    # ----- Now in the git module *build* directory ----- #
283
284    # Check for uncommitted/queued changes.
285    check_local_changes
286    if [ $? -ne 0 ]; then
287	cd $top_src
288	return 1
289    fi
290
291    # Determine what is the current branch and the remote name
292    current_branch=`git branch | $GREP "\*" | sed -e "s/\* //"`
293    remote_name=`git config --get branch.$current_branch.remote`
294    remote_branch=`git config --get branch.$current_branch.merge | cut -d'/' -f3,4`
295    echo "Info: working off the \"$current_branch\" branch tracking the remote \"$remote_name/$remote_branch\"."
296
297    # Run 'make dist/distcheck' to ensure the tarball matches the git module content
298    # Important to run make dist/distcheck before looking in Makefile, may need to reconfigure
299    echo "Info: running \"make $MAKE_DIST_CMD\" to create tarballs:"
300    ${MAKE} $MAKEFLAGS $MAKE_DIST_CMD > /dev/null
301    if [ $? -ne 0 ]; then
302	echo "Error: \"$MAKE $MAKEFLAGS $MAKE_DIST_CMD\" failed."
303	cd $top_src
304	return 1
305    fi
306
307    # Find out the tarname from the makefile
308    pkg_name=`$GREP '^PACKAGE = ' Makefile | sed 's|PACKAGE = ||'`
309    pkg_version=`$GREP '^VERSION = ' Makefile | sed 's|VERSION = ||'`
310    tar_name="$pkg_name-$pkg_version"
311    targz=$tar_name.tar.gz
312    tarbz2=$tar_name.tar.bz2
313    tarxz=$tar_name.tar.xz
314
315    [ -e $targz ] && ls -l $targz || unset targz
316    [ -e $tarbz2 ] && ls -l $tarbz2 || unset tarbz2
317    [ -e $tarxz ] && ls -l $tarxz || unset tarxz
318
319    if [ -z "$targz" -a -z "$tarbz2" -a -z "$tarxz" ]; then
320	echo "Error: no compatible tarballs found."
321	cd $top_src
322	return 1
323    fi
324
325    # wayland/weston/libinput tag with the version number only
326    tag_name="$tar_name"
327    if [ x"$section" = xwayland ] ||
328       [ x"$section" = xweston ] ||
329       [ x"$section" = xlibinput ]; then
330	tag_name="$pkg_version"
331    fi
332
333    # evemu tag with the version number prefixed by 'v'
334    if [ x"$section" = xevemu ]; then
335        tag_name="v$pkg_version"
336    fi
337
338    gpgsignerr=0
339    siggz="$(sign_or_fail ${targz})"
340    gpgsignerr=$((${gpgsignerr} + $?))
341    sigbz2="$(sign_or_fail ${tarbz2})"
342    gpgsignerr=$((${gpgsignerr} + $?))
343    sigxz="$(sign_or_fail ${tarxz})"
344    gpgsignerr=$((${gpgsignerr} + $?))
345    if [ ${gpgsignerr} -ne 0 ]; then
346        echo "Error: unable to sign at least one of the tarballs."
347        cd $top_src
348        return 1
349    fi
350
351    # Obtain the top commit SHA which should be the version bump
352    # It should not have been tagged yet (the script will do it later)
353    local_top_commit_sha=`git  rev-list --max-count=1 HEAD`
354    if [ $? -ne 0 ]; then
355	echo "Error: unable to obtain the local top commit id."
356	cd $top_src
357	return 1
358    fi
359
360    # Check that the top commit looks like a version bump
361    git diff --unified=0 HEAD^ | $GREP -F $pkg_version >/dev/null 2>&1
362    if [ $? -ne 0 ]; then
363	# Wayland repos use  m4_define([wayland_major_version], [0])
364	git diff --unified=0 HEAD^ | $GREP -E "(major|minor|micro)_version" >/dev/null 2>&1
365	if [ $? -ne 0 ]; then
366	    echo "Error: the local top commit does not look like a version bump."
367	    echo "       the diff does not contain the string \"$pkg_version\"."
368	    local_top_commit_descr=`git log --oneline --max-count=1 $local_top_commit_sha`
369	    echo "       the local top commit is: \"$local_top_commit_descr\""
370	    cd $top_src
371	    return 1
372	fi
373    fi
374
375    # Check that the top commit has been pushed to remote
376    remote_top_commit_sha=`git  rev-list --max-count=1 $remote_name/$remote_branch`
377    if [ $? -ne 0 ]; then
378	echo "Error: unable to obtain top commit from the remote repository."
379	cd $top_src
380	return 1
381    fi
382    if [ x"$remote_top_commit_sha" != x"$local_top_commit_sha" ]; then
383	echo "Error: the local top commit has not been pushed to the remote."
384	local_top_commit_descr=`git log --oneline --max-count=1 $local_top_commit_sha`
385	echo "       the local top commit is: \"$local_top_commit_descr\""
386	cd $top_src
387	return 1
388    fi
389
390    # If a tag exists with the the tar name, ensure it is tagging the top commit
391    # It may happen if the version set in configure.ac has been previously released
392    tagged_commit_sha=`git  rev-list --max-count=1 $tag_name 2>/dev/null`
393    if [ $? -eq 0 ]; then
394	# Check if the tag is pointing to the top commit
395	if [ x"$tagged_commit_sha" != x"$remote_top_commit_sha" ]; then
396	    echo "Error: the \"$tag_name\" already exists."
397	    echo "       this tag is not tagging the top commit."
398	    remote_top_commit_descr=`git log --oneline --max-count=1 $remote_top_commit_sha`
399	    echo "       the top commit is: \"$remote_top_commit_descr\""
400	    local_tag_commit_descr=`git log --oneline --max-count=1 $tagged_commit_sha`
401	    echo "       tag \"$tag_name\" is tagging some other commit: \"$local_tag_commit_descr\""
402	    cd $top_src
403	    return 1
404	else
405	    echo "Info: module already tagged with \"$tag_name\"."
406	fi
407    else
408	# Tag the top commit with the tar name
409	if [ x"$DRY_RUN" = x ]; then
410	    git tag -s -m $tag_name $tag_name
411	    if [ $? -ne 0 ]; then
412		echo "Error:  unable to tag module with \"$tag_name\"."
413		cd $top_src
414		return 1
415	    else
416		echo "Info: module tagged with \"$tag_name\"."
417	    fi
418	else
419	    echo "Info: skipping the commit tagging in dry-run mode."
420	fi
421    fi
422
423    # --------- Now the tarballs are ready to upload ----------
424
425    # The hostname which is used to connect to the development resources
426    hostname="annarchy.freedesktop.org"
427
428    # Some hostnames are also used as /srv subdirs
429    host_fdo="www.freedesktop.org"
430
431    list_to="virglrenderer-devel@lists.freedesktop.org"
432
433    host_current=$host_fdo
434    section_path=software/virgl
435    srv_path="/srv/$host_current/www/$section_path"
436
437    # Use personal web space on the host for unit testing (leave commented out)
438    # srv_path="~/public_html$srv_path"
439
440    # Check that the server path actually does exist
441    ssh $USER_NAME$hostname ls $srv_path >/dev/null 2>&1 ||
442    if [ $? -ne 0 ]; then
443	echo "Error: the path \"$srv_path\" on the web server does not exist."
444	cd $top_src
445	return 1
446    fi
447
448    # Check for already existing tarballs
449    for tarball in $targz $tarbz2 $tarxz; do
450	ssh $USER_NAME$hostname ls $srv_path/$tarball  >/dev/null 2>&1
451	if [ $? -eq 0 ]; then
452	    if [ "x$FORCE" = "xyes" ]; then
453		echo "Warning: overwriting released tarballs due to --force option."
454	    else
455		echo "Error: tarball $tar_name already exists. Use --force to overwrite."
456		cd $top_src
457		return 1
458	    fi
459	fi
460    done
461
462    # Upload to host using the 'scp' remote file copy program
463    if [ x"$DRY_RUN" = x ]; then
464	echo "Info: uploading tarballs to web server:"
465	scp $targz $tarbz2 $tarxz $siggz $sigbz2 $sigxz $USER_NAME$hostname:$srv_path
466	if [ $? -ne 0 ]; then
467	    echo "Error: the tarballs uploading failed."
468	    cd $top_src
469	    return 1
470	fi
471    else
472	echo "Info: skipping tarballs uploading in dry-run mode."
473	echo "      \"$srv_path\"."
474    fi
475
476    # Pushing the top commit tag to the remote repository
477    if [ x$DRY_RUN = x ]; then
478	echo "Info: pushing tag \"$tag_name\" to remote \"$remote_name\":"
479	git push $remote_name $tag_name
480	if [ $? -ne 0 ]; then
481	    echo "Error: unable to push tag \"$tag_name\" to the remote repository."
482	    echo "       it is recommended you fix this manually and not run the script again"
483	    cd $top_src
484	    return 1
485	fi
486    else
487	echo "Info: skipped pushing tag \"$tag_name\" to the remote repository in dry-run mode."
488    fi
489
490    MD5SUM=`which md5sum || which gmd5sum`
491    SHA1SUM=`which sha1sum || which gsha1sum`
492    SHA256SUM=`which sha256sum || which gsha256sum`
493
494    # --------- Generate the announce e-mail ------------------
495    # Failing to generate the announce is not considered a fatal error
496
497    # Git-describe returns only "the most recent tag", it may not be the expected one
498    # However, we only use it for the commit history which will be the same anyway.
499    tag_previous=`git describe --abbrev=0 HEAD^ 2>/dev/null`
500    # Git fails with rc=128 if no tags can be found prior to HEAD^
501    if [ $? -ne 0 ]; then
502	if [ $? -ne 0 ]; then
503	    echo "Warning: unable to find a previous tag."
504	    echo "         perhaps a first release on this branch."
505	    echo "         Please check the commit history in the announce."
506	fi
507    fi
508    if [ x"$tag_previous" != x ]; then
509	# The top commit may not have been tagged in dry-run mode. Use commit.
510	tag_range=$tag_previous..$local_top_commit_sha
511    else
512	tag_range=$tag_name
513    fi
514    generate_announce > "$tar_name.announce"
515    echo "Info: [ANNOUNCE] template generated in \"$tar_name.announce\" file."
516    echo "      Please pgp sign and send it."
517
518    # --------- Update the JH Build moduleset -----------------
519    # Failing to update the jh moduleset is not considered a fatal error
520    if [ x"$JH_MODULESET" != x ]; then
521	for tarball in $targz $tarbz2 $tarxz; do
522	    if [ x$DRY_RUN = x ]; then
523		sha1sum=`$SHA1SUM $tarball | cut -d' ' -f1`
524		$top_src/util/modular/update-moduleset.sh $JH_MODULESET $sha1sum $tarball
525		echo "Info: updated jh moduleset: \"$JH_MODULESET\""
526	    else
527		echo "Info: skipping jh moduleset \"$JH_MODULESET\" update in dry-run mode."
528	    fi
529
530	    # $tar* may be unset, so simply loop through all of them and the
531	    # first one that is set updates the module file
532	    break
533	done
534    fi
535
536
537    # --------- Successful completion --------------------------
538    cd $top_src
539    return 0
540
541}
542
543#------------------------------------------------------------------------------
544#			Function: usage
545#------------------------------------------------------------------------------
546# Displays the script usage and exits successfully
547#
548usage() {
549    basename="`expr "//$0" : '.*/\([^/]*\)'`"
550    cat <<HELP
551
552Usage: $basename [options] path...
553
554Where "path" is a relative path to a git module, including '.'.
555
556Options:
557  --dist              make 'dist' instead of 'distcheck'; use with caution
558  --distcheck         Default, ignored for compatibility
559  --dry-run           Does everything except tagging and uploading tarballs
560  --force             Force overwriting an existing release
561  --help              Display this help and exit successfully
562  --modfile <file>    Release the git modules specified in <file>
563  --moduleset <file>  The jhbuild moduleset full pathname to be updated
564  --no-quit           Do not quit after error; just print error message
565  --user <name>@      Username of your fdo account if not configured in ssh
566
567Environment variables defined by the "make" program and used by release.sh:
568  MAKE        The name of the make command [make]
569  MAKEFLAGS:  Options to pass to all \$(MAKE) invocations
570
571HELP
572}
573
574#------------------------------------------------------------------------------
575#			Script main line
576#------------------------------------------------------------------------------
577#
578
579# Choose which make program to use (could be gmake)
580MAKE=${MAKE:="make"}
581
582# Choose which grep program to use (on Solaris, must be gnu grep)
583if [ "x$GREP" = "x" ] ; then
584    if [ -x /usr/gnu/bin/grep ] ; then
585	GREP=/usr/gnu/bin/grep
586    else
587	GREP=grep
588    fi
589fi
590
591# Find path for GnuPG v2
592if [ "x$GPG" = "x" ] ; then
593    if [ -x /usr/bin/gpg2 ] ; then
594	GPG=/usr/bin/gpg2
595    else
596	GPG=gpg
597    fi
598fi
599
600# Set the default make tarball creation command
601MAKE_DIST_CMD=distcheck
602
603# Process command line args
604while [ $# != 0 ]
605do
606    case $1 in
607    # Use 'dist' rather than 'distcheck' to create tarballs
608    # You really only want to do this if you're releasing a module you can't
609    # possibly build-test.  Please consider carefully the wisdom of doing so.
610    --dist)
611	MAKE_DIST_CMD=dist
612	;;
613    # Use 'distcheck' to create tarballs
614    --distcheck)
615	MAKE_DIST_CMD=distcheck
616	;;
617    # Does everything except uploading tarball
618    --dry-run)
619	DRY_RUN=yes
620	;;
621    # Force overwriting an existing release
622    # Use only if nothing changed in the git repo
623    --force)
624	FORCE=yes
625	;;
626    # Display this help and exit successfully
627    --help)
628	usage
629	exit 0
630	;;
631    # Release the git modules specified in <file>
632    --modfile)
633	check_option_args $1 $2
634	shift
635	MODFILE=$1
636	;;
637    # The jhbuild moduleset to update with relase info
638    --moduleset)
639	check_option_args $1 $2
640	shift
641	JH_MODULESET=$1
642	;;
643    # Do not quit after error; just print error message
644    --no-quit)
645	NO_QUIT=yes
646	;;
647    # Username of your fdo account if not configured in ssh
648    --user)
649	check_option_args $1 $2
650	shift
651	USER_NAME=$1
652	;;
653    --*)
654	echo ""
655	echo "Error: unknown option: $1"
656	echo ""
657	usage
658	exit 1
659	;;
660    -*)
661	echo ""
662	echo "Error: unknown option: $1"
663	echo ""
664	usage
665	exit 1
666	;;
667    *)
668	if [ x"${MODFILE}" != x ]; then
669	    echo ""
670	    echo "Error: specifying both modules and --modfile is not permitted"
671	    echo ""
672	    usage
673	    exit 1
674	fi
675	INPUT_MODULES="${INPUT_MODULES} $1"
676	;;
677    esac
678
679    shift
680done
681
682# If no modules specified (blank cmd line) display help
683check_modules_specification
684
685# Read the module file and normalize input in INPUT_MODULES
686read_modfile
687
688# Loop through each module to release
689# Exit on error if --no-quit no specified
690process_modules
691
692# Print the epilog with final status
693print_epilog
694