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