├── .mailmap ├── tests ├── readme.txt ├── melting-pot-out-dir.t ├── melting-pot-simple.t ├── melting-pot-prune.t └── melting-pot-multi.t ├── duplicate-class-names.sh ├── ci-setup-github-actions.sh ├── git-hg-synchronizer.sh ├── remote-branch-info.sh ├── validate-gitignore.sh ├── UNLICENSE ├── check-branch.sh ├── dep-versions.pl ├── find-duplicate-classes.py ├── valid-semver-bump.sh ├── verify-checksums.sh ├── sj-version.sh ├── git-svn-synchronizer.sh ├── class-version.sh ├── git-cvs-synchronizer.sh ├── git-synchronizer.sh ├── github-actionify.sh ├── ci-build.sh ├── release-version.sh └── melting-pot.sh /.mailmap: -------------------------------------------------------------------------------- 1 | Jack Yuan <55564584+yongleyuan@users.noreply.github.com> 2 | Tobias Pietzsch 3 | -------------------------------------------------------------------------------- /tests/readme.txt: -------------------------------------------------------------------------------- 1 | This directory houses automated tests for use with cram. 2 | 3 | https://bitheap.org/cram/ 4 | 5 | To run them, type "cram tests" from the toplevel directory. 6 | -------------------------------------------------------------------------------- /tests/melting-pot-out-dir.t: -------------------------------------------------------------------------------- 1 | Script should fail if output directory already exists: 2 | 3 | $ mkdir melting-pot && sh "$TESTDIR/../melting-pot.sh" foo:bar 4 | [ERROR] Directory already exists: melting-pot 5 | [2] 6 | -------------------------------------------------------------------------------- /duplicate-class-names.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # duplicate-class-names.sh 5 | # 6 | 7 | # Script to print out classes in different packages with the same name. 8 | # 9 | # Such name clashes are to be avoided, since when more than one of them 10 | # is needed in the same source file, it becomes necessary to reference 11 | # all but one using fully qualified package prefixes. 12 | 13 | names=$(git ls-files \*.java | 14 | sed -e 's|.*/||' -e 's|\.java$||' | 15 | sort | 16 | uniq -d) 17 | for name in $names 18 | do 19 | printf '\t%s\n' $name 20 | git ls-files \*/$name.java 21 | done 22 | -------------------------------------------------------------------------------- /ci-setup-github-actions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # ci-setup-github-actions.sh - Set CI-related environment variables from GitHub Actions. 5 | # 6 | 7 | echo "BUILD_REPOSITORY=https://github.com/$GITHUB_REPOSITORY" 8 | echo "BUILD_REPOSITORY=https://github.com/$GITHUB_REPOSITORY" >> $GITHUB_ENV 9 | 10 | echo "BUILD_OS=$RUNNER_OS" 11 | echo "BUILD_OS=$RUNNER_OS" >> $GITHUB_ENV 12 | 13 | echo "BUILD_BASE_REF=$GITHUB_BASE_REF" 14 | echo "BUILD_BASE_REF=$GITHUB_BASE_REF" >> $GITHUB_ENV 15 | 16 | echo "BUILD_HEAD_REF=$GITHUB_HEAD_REF" 17 | echo "BUILD_HEAD_REF=$GITHUB_HEAD_REF" >> $GITHUB_ENV 18 | -------------------------------------------------------------------------------- /git-hg-synchronizer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script uses Git for Windows' remote-hg to mirror Mercurial repositories. 4 | # It is meant to be run as a Jenkins job. 5 | 6 | set -e 7 | 8 | type git-remote-hg > /dev/null 2>&1 || { 9 | mkdir -p "$HOME"/bin && 10 | if test ! -x "$HOME"/bin/git-remote-hg 11 | then 12 | curl -Lfs https://github.com/msysgit/git/raw/master/contrib/remote-helpers/git-remote-hg > "$HOME"/bin/git-remote-hg && 13 | chmod a+x "$HOME"/bin/git-remote-hg 14 | fi && 15 | export PATH="$HOME"/bin:$PATH 16 | } || { 17 | echo "Could not install git-remote-hg" >&2 18 | exit 1 19 | } 20 | 21 | HG_URL="$1" 22 | shift 23 | 24 | test -d .git || git init --bare 25 | test a"hg::$HG_URL" = a"$(git config remote.origin.url)" || 26 | git remote add --mirror=fetch origin hg::"$HG_URL" 27 | 28 | git fetch origin 29 | 30 | git gc --auto 31 | for url 32 | do 33 | git push --all "$url" 34 | git push --tags "$url" 35 | done 36 | -------------------------------------------------------------------------------- /remote-branch-info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple script to list last author and commit date, as well as number of 4 | # unmerged commits, for each remote branch of a given remote. 5 | # 6 | # This is useful for analyzing which branches are obsolete and/or moldy. 7 | 8 | remote="$*" 9 | if [ "$remote" == "" ]; 10 | then 11 | remote=$(git rev-parse --symbolic-full-name HEAD@{u}) 12 | remote=${remote%/*} 13 | remote=${remote#refs/remotes/} 14 | echo Using remote $remote 15 | fi 16 | 17 | case "$remote" in 18 | --help|-h) 19 | echo Please specify one of the following remotes: 20 | git remote 21 | exit 1 22 | ;; 23 | esac 24 | 25 | for ref in $(git for-each-ref refs/remotes/$remote --format='%(refname)') 26 | do 27 | headBranch=$(git remote show "$remote" | grep HEAD | sed 's/ *HEAD branch: //') 28 | refname=${ref#refs/remotes/$remote/} 29 | test "$refname" = "$headBranch" -o "$refname" = HEAD && continue 30 | unmerged_count=$(git cherry "$headBranch" "$ref" | grep '^+' | wc -l) 31 | author=$(git log -n 1 --format='%an' "$ref") 32 | timestamp=$(git log -n 1 --format='%ar' "$ref") 33 | echo "$refname~$author~$timestamp~$unmerged_count unmerged" 34 | done | column -t -s '~' 35 | -------------------------------------------------------------------------------- /validate-gitignore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script can be used to identify unnecessary entries in the .gitignore 4 | # file. 5 | # 6 | # Three modes are available: 7 | # 8 | # --validate 9 | # prints out unnecessary entries 10 | # --clean 11 | # prints out the contents of .gitignore, skipping unnecessary entries 12 | # --fix 13 | # overwrites .gitignore with the output of --clean 14 | 15 | die () { 16 | echo "$*" >&2 17 | exit 1 18 | } 19 | 20 | while test $# -gt 0 21 | do 22 | case "$1" in 23 | --fix|--validate|--clean) mode=${1#--};; 24 | *) die "Unknown command: $1";; 25 | esac 26 | shift 27 | done 28 | 29 | test -f .gitignore || 30 | die "No .gitignore file?" 31 | 32 | handle_line () { 33 | case "$1" in 34 | ''|\#*) 35 | printf '%s\n' "$1" 36 | continue 37 | ;; 38 | /*) 39 | if eval ls -d ."$1" > /dev/null 2>&1 40 | then 41 | printf "%s\n" "$1" 42 | elif test "validate" = "$mode" 43 | then 44 | echo "Unnecessary: $1" 1>&2 45 | fi 46 | ;; 47 | esac 48 | } 49 | 50 | cleaned="$(cat .gitignore | 51 | while read line 52 | do 53 | handle_line "$line" 54 | done)" 55 | 56 | case "$mode" in 57 | fix) 58 | printf "%s" "$cleaned" > .gitignore 59 | ;; 60 | clean) 61 | printf "%s" "$cleaned" 62 | ;; 63 | esac 64 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /check-branch.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | # check-branch.sh - Iterate over all new commits of a topic branch, 4 | # recording whether the build passes or fails for each. 5 | 6 | commits=$@ 7 | 8 | remote=$(git rev-parse --symbolic-full-name HEAD@{u}) 9 | remote=${remote#refs/remotes/} 10 | remote=${remote%%/*} 11 | headBranch=$(git remote show "$remote" | grep HEAD | sed 's/ *HEAD branch: //') 12 | 13 | test "$commits" || commits=$(git rev-list HEAD "^$remote/$headBranch" | sed '1!G;h;$!d') 14 | # NB: The sed line above reverses the order of the commits. 15 | # See: http://stackoverflow.com/a/744093 16 | 17 | branch=$(git rev-parse --abbrev-ref HEAD) 18 | 19 | count=0 20 | for commit in $commits 21 | do 22 | git checkout "$commit" > /dev/null 2>&1 23 | mkdir -p tmp 24 | prefix="$(printf %04d $count)" 25 | filename="tmp/$prefix-$commit" 26 | start=$(date +%s) 27 | if [ -f Makefile ] 28 | then 29 | make test > "$filename" 2>&1 && result=SUCCESS || result=FAILURE 30 | elif [ -f pom.xml ] 31 | then 32 | mvn clean verify > "$filename" 2>&1 && result=SUCCESS || result=FAILURE 33 | else 34 | result=SKIPPED 35 | fi 36 | end=$(date +%s) 37 | time=$(expr "$end" - "$start") 38 | echo "$prefix $commit $result $time" 39 | count=$(expr "$count" + 1) 40 | done 41 | 42 | git checkout "$branch" > /dev/null 2>&1 43 | -------------------------------------------------------------------------------- /tests/melting-pot-simple.t: -------------------------------------------------------------------------------- 1 | Down-the-middle test of a relatively simply project: 2 | 3 | $ sh "$TESTDIR/../melting-pot.sh" net.imagej:imagej-common:0.15.1 -r https://maven.scijava.org/content/groups/public -c org.scijava:scijava-common:2.44.2 -i 'org.scijava:*,net.imagej:*,net.imglib2:*,io.scif:*' -e net.imglib2:imglib2-roi -v -f -s 4 | [INFO] net.imagej:imagej-common:0.15.1: fetching project source 5 | [INFO] net.imagej:imagej-common:0.15.1: determining project dependencies 6 | [INFO] net.imagej:imagej-common:0.15.1: processing project dependencies 7 | [INFO] net.imagej:imagej-common:0.15.1: imglib2: fetching component source 8 | [INFO] net.imagej:imagej-common:0.15.1: processing changed components 9 | [INFO] Generating aggregator POM 10 | [INFO] Skipping the build; the command would have been: 11 | [INFO] mvn -Denforcer.skip -Dgentyref.version=1.1.0 -Dudunits.version=4.3.18 -Djunit.version=4.11 -Dimglib2-roi.version=0.3.0 -Dimglib2.version=2.2.1 -Dtrove4j.version=3.0.3 -Deventbus.version=1.4 -Dhamcrest-core.version=1.3 -Dscijava-common.version=2.44.2 test 12 | [INFO] net.imagej:imagej-common:0.15.1: complete 13 | 14 | $ find melting-pot -maxdepth 2 | sort 15 | melting-pot 16 | melting-pot/net.imagej 17 | melting-pot/net.imagej/imagej-common 18 | melting-pot/net.imglib2 19 | melting-pot/net.imglib2/imglib2 20 | melting-pot/pom.xml 21 | -------------------------------------------------------------------------------- /dep-versions.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # dep-versions.pl - reports the minimum version of Java 4 | # needed for each dependency of a project. 5 | 6 | use strict; 7 | 8 | my @deps = `mvn -B dependency:list`; 9 | 10 | my $active = 0; 11 | for my $dep (@deps) { 12 | chomp $dep; 13 | 14 | if (!$active) { 15 | $active = $dep =~ /The following files have been resolved/; 16 | next; 17 | } 18 | 19 | # parse GAPCVS from dependency output 20 | $dep =~ s/^\[INFO\]\s*//; 21 | my @t = split(/:/, $dep); 22 | my $tokens = @t; 23 | my $g, my $a, my $p, my $c, my $v, my $s; 24 | if ($tokens == 1) { 25 | # end of dependencies 26 | last; 27 | } 28 | elsif ($tokens == 5) { 29 | # e.g.: org.jruby:jruby-core:jar:1.7.12:runtime 30 | ($g, $a, $p, $v, $s) = @t; 31 | $c = ''; 32 | } 33 | elsif ($tokens == 6) { 34 | # e.g.: org.jogamp.jocl:jocl:jar:natives-linux-i586:2.3.2:runtime 35 | ($g, $a, $p, $c, $v, $s) = @t; 36 | } 37 | else { 38 | die "Unknown dependency format: $dep"; 39 | } 40 | 41 | # convert GAPCVS to local repository cache path 42 | my $gPart = $g; 43 | $gPart =~ s/\./\//g; 44 | my $cPart = $c ? "-$c" : ''; 45 | # e.g.: ~/.m2/repository/org/jogamp/jocl/jocl/2.3.2/jocl-2.3.2-natives-linux-i586.jar 46 | my $path = "\$HOME/.m2/repository/$gPart/$a/$v/$a-$v$cPart.$p"; 47 | 48 | # report Java version of the component 49 | print `class-version.sh "$path"`; 50 | } 51 | -------------------------------------------------------------------------------- /find-duplicate-classes.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | 3 | import os, subprocess 4 | 5 | def extract_classes(f): 6 | lines = subprocess.check_output(['jar', 'tf', f]).split() 7 | result = set() 8 | for line in lines: 9 | v = line.decode('utf-8').strip() 10 | if v.endswith('.class') and not v.endswith('module-info.class'): 11 | result.add(v) 12 | return result 13 | 14 | print('Reading JAR files... ', end='', flush=True) 15 | paths = [] 16 | for root, dirs, files in os.walk('.'): 17 | for name in files: 18 | if not name.lower().endswith('.jar'): continue 19 | paths.append(os.path.join(root, name)) 20 | paths.sort() 21 | 22 | classes = {} 23 | count = 0 24 | perc = '' 25 | for path in paths: 26 | count += 1 27 | classes[path] = extract_classes(path) 28 | print('\b' * len(perc), end='') 29 | perc = str(round(100 * count / len(paths))) + '% ' 30 | print(perc, end='', flush=True) 31 | 32 | print() 33 | print('Scanning for duplicate classes...') 34 | for i1 in range(len(paths)): 35 | p1 = paths[i1] 36 | duplist = [] 37 | for i2 in range(i1 + 1, len(paths)): 38 | p2 = paths[i2] 39 | dups = classes[p1].intersection(classes[p2]) 40 | if len(dups) > 0: 41 | duplist.append(f'==> {p2} (e.g. {next(iter(dups))})') 42 | if len(duplist) > 0: 43 | print(p1) 44 | for line in duplist: 45 | print(line) 46 | 47 | print('Done!') 48 | -------------------------------------------------------------------------------- /tests/melting-pot-prune.t: -------------------------------------------------------------------------------- 1 | Test that the '--prune' flag works as intended: 2 | 3 | $ sh "$TESTDIR/../melting-pot.sh" net.imagej:imagej-common:0.15.1 -r https://maven.scijava.org/content/groups/public -c org.scijava:scijava-common:2.44.2 -i 'org.scijava:*,net.imagej:*,net.imglib2:*' -p -v -f -s 4 | [INFO] net.imagej:imagej-common:0.15.1: fetching project source 5 | [INFO] net.imagej:imagej-common:0.15.1: determining project dependencies 6 | [INFO] net.imagej:imagej-common:0.15.1: processing project dependencies 7 | [INFO] net.imagej:imagej-common:0.15.1: imglib2-roi: fetching component source 8 | [INFO] net.imagej:imagej-common:0.15.1: imglib2: fetching component source 9 | [INFO] net.imagej:imagej-common:0.15.1: processing changed components 10 | [INFO] Checking relevance of component net.imagej/imagej-common 11 | [INFO] Checking relevance of component net.imglib2/imglib2 12 | [INFO] Pruning irrelevant component: net.imglib2/imglib2 13 | [INFO] Checking relevance of component net.imglib2/imglib2-roi 14 | [INFO] Pruning irrelevant component: net.imglib2/imglib2-roi 15 | [INFO] Generating aggregator POM 16 | [INFO] Skipping the build; the command would have been: 17 | [INFO] mvn -Denforcer.skip -Dgentyref.version=1.1.0 -Dudunits.version=4.3.18 -Djunit.version=4.11 -Dimglib2-roi.version=0.3.0 -Dimglib2.version=2.2.1 -Dtrove4j.version=3.0.3 -Deventbus.version=1.4 -Dhamcrest-core.version=1.3 -Dscijava-common.version=2.44.2 test 18 | [INFO] net.imagej:imagej-common:0.15.1: complete 19 | 20 | $ find melting-pot -maxdepth 2 | sort 21 | melting-pot 22 | melting-pot/net.imagej 23 | melting-pot/net.imagej/imagej-common 24 | melting-pot/net.imglib2 25 | melting-pot/pom.xml 26 | -------------------------------------------------------------------------------- /valid-semver-bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script tests whether a given version bump is valid. 4 | 5 | die() { 6 | echo "$*" >&2 7 | exit 1 8 | } 9 | 10 | succeed() { 11 | echo "$*" >&2 12 | exit 0 13 | } 14 | 15 | major() { 16 | major=${1%%.*} 17 | test -n "$major" || die "Invalid SemVer: $1" 18 | echo $major 19 | } 20 | 21 | minor() { 22 | tmp=${1#*.} 23 | minor=${tmp%%.*} 24 | test -n "$minor" || die "Invalid SemVer: $1" 25 | echo $minor 26 | } 27 | 28 | patch() { 29 | patch=${1##*.} 30 | test -n "$patch" || die "Invalid SemVer: $1" 31 | echo $patch 32 | } 33 | 34 | SNAPSHOT="${1%-SNAPSHOT}" 35 | RELEASE="$2" 36 | 37 | test -n "$RELEASE" -a -n "$SNAPSHOT" || 38 | die "Usage: valid-semver-bump.sh previous-snapshot-version new-release-version" 39 | 40 | test "$RELEASE" = "$SNAPSHOT" && 41 | succeed "Detected DEFAULT version bump" 42 | 43 | NEW_MAJOR=$(major $RELEASE) 44 | NEW_MINOR=$(minor $RELEASE) 45 | NEW_PATCH=$(patch $RELEASE) 46 | OLD_MAJOR=$(major $SNAPSHOT) 47 | OLD_MINOR=$(minor $SNAPSHOT) 48 | OLD_PATCH=$(patch $SNAPSHOT) 49 | 50 | # check for MINOR version bump 51 | # e.g. 1.0.1-SNAPSHOT -> 1.1.0 52 | if [ "$OLD_PATCH" -gt 0 ] 53 | then 54 | test "$NEW_MAJOR" -eq "$OLD_MAJOR" \ 55 | -a "$NEW_MINOR" -eq "$((OLD_MINOR+1))" \ 56 | -a "$NEW_PATCH" -eq 0 && 57 | succeed "Detected MINOR version bump" 58 | fi 59 | 60 | # check for MAJOR version bump 61 | # e.g. 1.1.0-SNAPSHOT -> 2.0.0 62 | # e.g. 1.0.1-SNAPSHOT -> 2.0.0 63 | if [ "$OLD_PATCH" -gt 0 -o "$OLD_MINOR" -gt 0 ] 64 | then 65 | test "$NEW_MAJOR" -eq "$((OLD_MAJOR+1))" \ 66 | -a "$NEW_MINOR" -eq 0 \ 67 | -a "$NEW_PATCH" -eq 0 && 68 | succeed "Detected MAJOR version bump" 69 | fi 70 | 71 | die "Invalid version bump: $SNAPSHOT -> $RELEASE" 72 | -------------------------------------------------------------------------------- /verify-checksums.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # verify-checksums.sh - A script to check .md5 and .sha1 checksums. 4 | # Because apparently there is no built-in way to do this en masse? 5 | # And there is no Maven plugin to do it either? Shocking! ;-) 6 | 7 | if [ "$1" == "" ] 8 | then 9 | echo "Usage: verify-checksum.sh [-v] [ ...]" 10 | exit 1 11 | fi 12 | 13 | verbose="" 14 | 15 | for arg in $@ 16 | do 17 | dir="" 18 | case "$arg" in 19 | -v) 20 | verbose=1 21 | ;; 22 | *) 23 | dir="$arg" 24 | ;; 25 | esac 26 | test -n "$dir" || continue 27 | 28 | if [ ! -d "$dir" ] 29 | then 30 | echo "Warning: skipping invalid directory: $dir" 31 | continue 32 | fi 33 | 34 | # verify MD5 checksums 35 | find "$dir" -name '*.md5' -print0 | while read -d $'\0' -r md5 36 | do 37 | file="${md5%.md5}" 38 | if [ ! -f "$file" ] 39 | then 40 | echo "[FAIL] $file: file does not exist" 41 | continue 42 | fi 43 | expected="$(cat "$md5")" 44 | expected="${expected:0:32}" 45 | actual="$(md5sum "$file" | cut -d ' ' -f 1)" 46 | if [ "$expected" == "$actual" ] 47 | then 48 | test "$verbose" && echo "[PASS] $file" 49 | else 50 | echo "[FAIL] $file: $expected != $actual" 51 | fi 52 | done 53 | 54 | # verify SHA-1 checksums 55 | find "$dir" -name '*.sha1' -print0 | while read -d $'\0' -r sha1 56 | do 57 | file="${sha1%.sha1}" 58 | if [ ! -f "$file" ] 59 | then 60 | echo "[FAIL] $file: file does not exist" 61 | continue 62 | fi 63 | expected="$(cat "$sha1")" 64 | expected="${expected:0:40}" 65 | actual="$(sha1sum "$file" | cut -d ' ' -f 1)" 66 | if [ "$expected" == "$actual" ] 67 | then 68 | test "$verbose" && echo "[PASS] $file" 69 | else 70 | echo "[FAIL] $file: $expected != $actual" 71 | fi 72 | done 73 | done 74 | -------------------------------------------------------------------------------- /sj-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script to print version properties for a given pom-scijava release. 4 | 5 | # Examples: 6 | # sj-version.sh 1.70 7 | # sj-version.sh 1.70 1.74 8 | 9 | version="$1" 10 | diff="$2" 11 | 12 | repo="https://maven.scijava.org/content/groups/public" 13 | 14 | props() { 15 | if [ -e "$1" ] 16 | then 17 | # extract version properties from the given file path 18 | pomContent=$(cat "$1") 19 | else 20 | # assume argument is a version number of pom-scijava 21 | pomURL="$repo/org/scijava/pom-scijava/$1/pom-scijava-$1.pom" 22 | pomContent=$(curl -s "$pomURL") 23 | fi 24 | 25 | # grep the pom-scijava-base parent version of out of the POM, 26 | # then rip out the version properties from that one as well! 27 | psbVersion=$(echo "$pomContent" | 28 | grep -A1 'pom-scijava-base' | 29 | grep '' | sed 's;.*>\([^<]*\)<.*;\1;') 30 | psbContent= 31 | if [ "$psbVersion" ] 32 | then 33 | psbURL="$repo/org/scijava/pom-scijava-base/$psbVersion/pom-scijava-base-$psbVersion.pom" 34 | psbContent=$(curl -s "$psbURL") 35 | fi 36 | 37 | { echo "$pomContent"; echo "$psbContent"; } | 38 | grep '\.version>' | 39 | sed -E -e 's/^ (.*)/\1 [DEV]/' | 40 | sed -E -e 's/^ *<(.*)\.version>(.*)<\/.*\.version>/\1 = \2/' | 41 | sort 42 | } 43 | 44 | if [ -z "$version" ] 45 | then 46 | # try to extract version from pom.xml in this directory 47 | if [ -e pom.xml ] 48 | then 49 | version=$(grep -A 1 pom-scijava pom.xml | \ 50 | grep '' | \ 51 | sed 's/<\/.*//' | \ 52 | sed 's/.*>//') 53 | fi 54 | fi 55 | 56 | if [ -z "$version" ] 57 | then 58 | echo "Usage: sj-version.sh version [version-to-diff]" 59 | exit 1 60 | fi 61 | 62 | if [ -n "$diff" ] 63 | then 64 | # compare two versions 65 | props $version > $version.tmp 66 | props $diff > $diff.tmp 67 | diff -y $version.tmp $diff.tmp 68 | rm $version.tmp $diff.tmp 69 | else 70 | # dump props for one version 71 | props $version 72 | fi 73 | -------------------------------------------------------------------------------- /git-svn-synchronizer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script synchronizes a Git repository with a given Subversion repository. 4 | # Originally specific to ImageJ2 (before that project switched over to Git), 5 | # it accepts the repository URLs via the command-line, e.g. for use in a 6 | # Jenkins job. 7 | # 8 | # It takes the Subversion URL as first parameter and an arbitrary number of 9 | # Git push URLs after that. 10 | # 11 | # Example: 12 | # 13 | # git-svn-synchronizer.sh \ 14 | # https://valelab.ucsf.edu/svn/micromanager2/ \ 15 | # github.com:openspim/micromanager \ 16 | # fiji.sc:/srv/git/micromanager1.4/.git \ 17 | 18 | usage () { 19 | echo "Usage: $0 [--force] [--reset] ..." >&2 20 | exit 1 21 | } 22 | 23 | force= 24 | reset= 25 | while test $# -gt 0 26 | do 27 | case "$1" in 28 | --force) 29 | force=--force 30 | ;; 31 | --reset) 32 | git branch -D -r $(git branch -r) 33 | rm -rf .git/svn 34 | git config --remove-section svn-remote.svn 35 | reset=t 36 | ;; 37 | -*) 38 | usage 39 | ;; 40 | *) 41 | break 42 | ;; 43 | esac 44 | shift 45 | done 46 | 47 | if test $# -lt 2 48 | then 49 | usage 50 | fi 51 | 52 | set -e 53 | 54 | SVN_URL="$1" 55 | shift 56 | 57 | # initialize repository 58 | if ! test -d .git 59 | then 60 | git init && 61 | git config core.bare true && 62 | if test -z "$reset" 63 | then 64 | git remote add -f tmp "$1" && 65 | # read from remote repository to prevent unnecessary git-svn cloning 66 | git for-each-ref --format '%(refname)' refs/remotes/tmp/svn/ | 67 | while read ref 68 | do 69 | git push . $ref:refs/remotes/${ref#refs/remotes/tmp/svn/} 70 | done && 71 | git remote rm tmp 72 | fi 73 | fi 74 | 75 | # initialize the Subversion URL 76 | if ! test -d .git/svn || test "a${SVN_URL%/}" != "a$(git config svn-remote.svn.url)" 77 | then 78 | # Try standard trunk/branches/tags setup first 79 | git svn init -s "$SVN_URL" && 80 | git svn fetch && 81 | git rev-parse refs/remotes/trunk || 82 | git rev-parse refs/remotes/git-svn || { 83 | rm -rf .git/svn && 84 | git config --remove-section svn-remote.svn && 85 | git svn init "$SVN_URL" && 86 | git svn fetch 87 | } 88 | else 89 | git svn fetch 90 | fi 91 | 92 | # push refs/remote/* to refs/heads/svn/* 93 | args="$(git for-each-ref --shell --format '%(refname)' refs/remotes/ | 94 | sed -e "s/^'\(refs\/remotes\/\(.*\)\)'$/'\1:refs\/heads\/svn\/\2'/" \ 95 | -e 's/:refs\/heads\/svn\/tags\//:refs\/tags\//')" 96 | 97 | git gc --auto 98 | for remote 99 | do 100 | eval git push $force \"$remote\" $args 101 | done 102 | -------------------------------------------------------------------------------- /class-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # class-version.sh - find the Java version which wrote a JAR file 4 | 5 | class_version() { 6 | # extract bytes 4-7 7 | info=$(head -c 8 | hexdump -e '4/1 "%d\n" "\n"' | tail -n4) 8 | minor1="$(echo "$info" | sed -n 1p)" 9 | minor2="$(echo "$info" | sed -n 2p)" 10 | major1="$(echo "$info" | sed -n 3p)" 11 | major2="$(echo "$info" | sed -n 4p)" 12 | 13 | # compute major.minor version 14 | minor="$(expr 256 \* $minor1 + $minor2)" 15 | major="$(expr 256 \* $major1 + $major2)" 16 | 17 | # derive Java version 18 | case $major in 19 | 45) 20 | version="Java 1.0/1.1" 21 | ;; 22 | 46) 23 | version="Java 1.2" 24 | ;; 25 | 47) 26 | version="Java 1.3" 27 | ;; 28 | 48) 29 | version="Java 1.4" 30 | ;; 31 | 49) 32 | version="Java 5" 33 | ;; 34 | *) 35 | if [ "$major" -gt 49 ] 36 | then 37 | version="Java $(expr $major - 44)" 38 | else 39 | version="Unknown" 40 | fi 41 | ;; 42 | esac 43 | 44 | # report the results 45 | echo "$version ($major.$minor)" 46 | } 47 | 48 | first_class() { 49 | jar tf "$1" | 50 | grep '\.class$' | 51 | grep -v '^META-INF/' | 52 | grep -v 'module-info\.class' | 53 | head -n 1 54 | } 55 | 56 | for arg in "$@" 57 | do 58 | # Resolve Maven dependency coordinates into local files. 59 | case "$arg" in 60 | *:*:*:*) # g:a:v:c 61 | gav=${arg%:*} 62 | g=${gav%%:*} 63 | av=${gav#*:} 64 | a=${av%:*} 65 | v=${av#*:} 66 | c=${arg##*:} 67 | f="$HOME/.m2/repository/$(echo "$g" | tr '.' '/')/$a/$v/$a-$v-$c.jar" 68 | test -f "$f" || mvn dependency:get -Dartifact="$g:$a:$v:jar:$c" 69 | arg="$f" 70 | ;; 71 | *:*:*) # g:a:v 72 | ga=${arg%:*} 73 | g=${ga%%:*} 74 | a=${ga#*:} 75 | v=${arg##*:} 76 | f="$HOME/.m2/repository/$(echo "$g" | tr '.' '/')/$a/$v/$a-$v.jar" 77 | test -f "$f" || mvn dependency:get -Dartifact="$arg" 78 | arg="$f" 79 | ;; 80 | esac 81 | # Handle the various local file cases. 82 | case "$arg" in 83 | *.class) 84 | version=$(cat "$arg" | class_version) 85 | ;; 86 | *.jar) 87 | class=$(first_class "$arg") 88 | if [ -z "$class" ] 89 | then 90 | echo "$arg: No classes" 91 | continue 92 | fi 93 | version=$(unzip -p "$arg" "$class" | class_version) 94 | ;; 95 | *) 96 | >&2 echo "Unsupported argument: $arg" 97 | continue 98 | esac 99 | 100 | # Report the results. 101 | echo "$arg: $version" 102 | done 103 | -------------------------------------------------------------------------------- /tests/melting-pot-multi.t: -------------------------------------------------------------------------------- 1 | Test that recursive SCM retrieval and multi-module projects work: 2 | 3 | $ sh "$TESTDIR/../melting-pot.sh" sc.fiji:TrakEM2_:1.0f -r https://maven.scijava.org/content/groups/public -i 'sc.fiji:TrakEM2_' -v -s -d -f 4 | [INFO] sc.fiji:TrakEM2_:1.0f: fetching project source 5 | \+ xmllint --xpath ".*'project'.*'scm'.*'connection'.*" ".*/sc/fiji/TrakEM2_/1.0f/TrakEM2_-1.0f.pom" (re) 6 | \+ xmllint --xpath ".*'project'.*'parent'.*'groupId'.*" ".*/sc/fiji/TrakEM2_/1.0f/TrakEM2_-1.0f.pom" (re) 7 | \+ xmllint --xpath ".*'project'.*'parent'.*'artifactId'.*" ".*/sc/fiji/TrakEM2_/1.0f/TrakEM2_-1.0f.pom" (re) 8 | \+ xmllint --xpath ".*'project'.*'parent'.*'version'.*" ".*/sc/fiji/TrakEM2_/1.0f/TrakEM2_-1.0f.pom" (re) 9 | \+ xmllint --xpath ".*'project'.*'scm'.*'connection'.*" ".*/sc/fiji/pom-trakem2/1.3.2/pom-trakem2-1.3.2.pom" (re) 10 | + git clone "git://github.com/trakem2/TrakEM2" --branch "TrakEM2_-1.0f" --depth 1 "sc.fiji/TrakEM2_" 11 | [INFO] sc.fiji:TrakEM2_:1.0f: determining project dependencies 12 | + mvn dependency:list 13 | [INFO] sc.fiji:TrakEM2_:1.0f: processing project dependencies 14 | [INFO] sc.fiji:TrakEM2_:1.0f: processing changed components 15 | [INFO] Generating aggregator POM 16 | + xmllint --xpath "//*[local-name()='project']/*[local-name()='artifactId']" "sc.fiji/TrakEM2_/pom.xml" 17 | + xmllint --xpath "//*[local-name()='project']/*[local-name()='artifactId']" "sc.fiji/TrakEM2_/TrakEM2_/pom.xml" 18 | [INFO] Skipping the build; the command would have been: 19 | [INFO] mvn -Denforcer.skip -Dbatik.version=1.8 -Dlogback-classic.version=1.1.1 -Dlogback-core.version=1.1.1 -Dkryo.version=2.21 -Dgentyref.version=1.1.0 -Djgoodies-common.version=1.7.0 -Djgoodies-forms.version=1.7.2 -Djai-codec.version=1.1.3 -Dtools.version=1.4.2 -Dtools.version=1.4.2 -Dmines-jtk.version=20100113 -Dudunits.version=4.3.18 -Djama.version=1.0.3 -Djama.version=1.0.3 -Dj3d-core-utils.version=1.5.2 -Dj3d-core.version=1.5.2 -Dvecmath.version=1.5.2 -Djai-core.version=1.1.3 -Djoda-time.version=2.3 -Djunit.version=4.11 -Dmpicbg.version=1.0.1 -Dmpicbg.version=1.0.1 -Dmpicbg_.version=1.0.1 -Dij1-patcher.version=0.12.0 -Dij.version=1.49p -Dij.version=1.49p -Dimagej-common.version=0.12.2 -Dimglib2-ij.version=2.0.0-beta-30 -Dimglib2-roi.version=0.3.0 -Dimglib2.version=2.2.1 -Dtrove4j.version=3.0.3 -Dnone.version= -Dformats-api.version=5.0.7 -Dformats-bsd.version=5.0.7 -Dformats-common.version=5.0.7 -Djai_imageio.version=5.0.7 -Dome-xml.version=5.0.7 -Dspecification.version=5.0.7 -Dturbojpeg.version=5.0.7 -Dcommons-math3.version=3.4.1 -Deventbus.version=1.4 -Dhamcrest-core.version=1.3 -Djavassist.version=3.16.1-GA -Djcommon.version=1.0.23 -Djfreechart.version=1.0.19 -Dperf4j.version=0.9.13 -Djython-shaded.version=2.5.3 -Dnative-lib-loader.version=2.0.2 -Dscijava-common.version=2.39.0 -Dslf4j-api.version=1.7.6 -Dpostgresql.version=8.2-507.jdbc3 -D3D_Viewer.version=3.0.1 -DAnalyzeSkeleton_.version=2.0.4 -DFiji_Plugins.version=3.0.0 -DLasso_and_Blow_Tool.version=2.0.1 -DSimple_Neurite_Tracer.version=2.0.3 -DSkeletonize3D_.version=1.0.1 -DVIB-lib.version=2.0.1 -DVIB_.version=2.0.2 -DVectorString.version=1.0.2 -DbUnwarpJ_.version=2.6.2 -Dfiji-lib.version=2.1.0 -Dlegacy-imglib1.version=1.1.2-DEPRECATED -Dlevel_sets.version=1.0.1 -Dmpicbg-trakem2.version=1.2.2 -Dpal-optimization.version=2.0.0 test 20 | [INFO] sc.fiji:TrakEM2_:1.0f: complete 21 | 22 | $ find melting-pot -maxdepth 2 | sort 23 | melting-pot 24 | melting-pot/pom.xml 25 | melting-pot/sc.fiji 26 | melting-pot/sc.fiji/TrakEM2_ 27 | 28 | $ grep 'sc.fiji/TrakEM2_/TrakEM2_' melting-pot/pom.xml 29 | \t\tsc.fiji/TrakEM2_/TrakEM2_ (esc) 30 | -------------------------------------------------------------------------------- /git-cvs-synchronizer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script uses rsync and cvs2git for Sourceforge projects, and cvsimport 4 | # for all other CVS projects, to keep a Git mirror of CVS repositories 5 | # 6 | # Usage: git-cvs-synchronizer.sh ... 7 | # 8 | # where is the the CVS root followed by a colon and the name of the 9 | # CVS module. 10 | # 11 | # Example: git-cvs-synchronizer.sh \ 12 | # :pserver:anonymous@tcljava.cvs.sourceforge.net:/cvsroot/tcljava:tcljava \ 13 | # fiji.sc:/srv/git/tcljava/.git 14 | # 15 | # It uses either rsync (in the case of Sourceforge repositories) or cvsclone 16 | # to mirror the ,v files making up the CVS repository first and then calls 17 | # cvs2git (of the cvs2svn package by Michael Haggerty) to convert the full 18 | # repository into a Git one. This is both faster and more precise than git 19 | # cvsimport, but it comes at the price of being non-incremental (hence the 20 | # need to rsync or cvsclone, to make everything a bit faster). 21 | 22 | if test $# -lt 2 23 | then 24 | echo "Usage: $0 : ..." >&2 25 | exit 1 26 | fi 27 | 28 | set -e 29 | 30 | CVSROOT="${1%:*}" 31 | CVSMODULE="${1##*:}" 32 | shift 33 | 34 | RSYNC_URL="$(echo "$CVSROOT" | 35 | sed -n 's/^:pserver:anonymous@\([^:]*\.cvs\.\(sourceforge\|sf\)\.net\):\/\(cvsroot\/.*\)/\1::\3/p')" 36 | 37 | cvsclone () { 38 | mkdir -p cvs-mirror/ 39 | (cd cvs-mirror && 40 | test -d "$CVSCLONE" || { 41 | CVSCLONE=../../../cvsclone 42 | test -d "$CVSCLONE" || 43 | git clone git://repo.or.cz/cvsclone.git "$CVSCLONE" 44 | } 45 | test -x "$CVSCLONE"/cvsclone || 46 | (cd "$CVSCLONE" && make) 47 | "$CVSCLONE"/cvsclone "$@") 48 | } 49 | 50 | cvs2git () { 51 | test -d "$CVS2SVN" || { 52 | CVS2SVN=../../cvs2svn 53 | test -d "$CVS2SVN" || 54 | git clone git://fiji.sc/cvs2svn "$CVS2SVN" 55 | } 56 | ENCODING= 57 | case "$CVSMODULE" in 58 | oprofile) 59 | ENCODING=--encoding=iso-8859-1 60 | ;; 61 | esac 62 | "$CVS2SVN"/cvs2git --blobfile=cvs.blobs --dumpfile=cvs.dump \ 63 | --username=git-synchronizer $ENCODING cvs-mirror/ 64 | cat cvs.blobs cvs.dump | 65 | git fast-import 66 | } 67 | 68 | if test -n "$RSYNC_URL" 69 | then 70 | rsync -va "$RSYNC_URL/$CVSMODULE" cvs-mirror 71 | mkdir -p cvs-mirror/CVSROOT 72 | test -d .git || { 73 | git init 74 | mkdir -p .git/info 75 | echo /cvs-mirror/ >> .git/info/exclude 76 | } 77 | cvs2git 78 | elif cvsclone -d "$CVSROOT" "$CVSMODULE" 79 | then 80 | mkdir -p cvs-mirror/CVSROOT 81 | test -d .git || { 82 | git init 83 | mkdir -p .git/info 84 | echo /cvs-mirror/ >> .git/info/exclude 85 | } 86 | cvs2git 87 | else 88 | # fall back to cvsimport 89 | cvs -d "$CVSROOT" co "$CVSMODULE" 90 | cd "$CVSMODULE" 91 | case "$CVSROOT" in 92 | *cvs.dev.java.net*) 93 | EXTRA_CVSPS_OPTS="-p --no-rlog,--no-cvs-direct" 94 | ;; 95 | *cvs.scms.waikato.ac.nz*) 96 | (cvs rlog $(cat CVS/Repository) | 97 | sed "/^The changelog prior to shifting was:$/,/^=\{77\}$/d" \ 98 | > rlog-patched.out) 2>&1 | 99 | grep -ve "^rlog" -e "^connect" -e "^cvs r\?log" -e "^creating" 100 | EXTRA_CVSPS_OPTS="-p --test-log,rlog-patched.out" 101 | ;; 102 | *) 103 | EXTRA_CVSPS_OPTS= 104 | ;; 105 | esac 106 | git cvsimport -i -k $EXTRA_CVSPS_OPTS 107 | fi 108 | 109 | refs="$(git for-each-ref --shell --format '%(refname)')" 110 | 111 | git gc --auto 112 | for remote 113 | do 114 | eval git push \"$remote\" $refs 115 | done 116 | -------------------------------------------------------------------------------- /git-synchronizer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script wants to synchronize multiple public Git repositories with each 4 | # other 5 | # 6 | # Use it (e.g. in a Jenkins job) like this: 7 | # 8 | # $0 ... 9 | # 10 | # where Git-URL is either a push URL, or a pair of a fetch and a push URL 11 | # separated by an equal sign. 12 | # 13 | # Example: 14 | # 15 | # git-synchronizer.sh \ 16 | # git://fiji.sc/imglib.git=fiji.sc:/srv/git/imglib.git \ 17 | # git://github.com/imglib/imglib=github.com:imglib/imglib 18 | 19 | errors= 20 | add_error () { 21 | errors="$(printf "%s\n\n%s\n\n" "$errors" "$*")" 22 | } 23 | 24 | url2remotename () { 25 | echo "${1%=*}" | 26 | sed 's/[^-A-Za-z0-9._]/_/g' 27 | } 28 | 29 | nullsha1=0000000000000000000000000000000000000000 30 | find_deleted () { 31 | test -n "$2" || return 32 | printf '%s\n%s\n%s\n' "$1" "$2" "$2" | 33 | sort -k 3 | 34 | uniq -u -f 2 | 35 | sed "s/^.\{40\}/$nullsha1/" 36 | } 37 | 38 | find_modified () { 39 | printf '%s\n%s\n' "$2" "$1" | 40 | sort -s -k 3 | 41 | uniq -u | 42 | uniq -d -f 2 | 43 | uniq -f 2 44 | } 45 | 46 | find_new () { 47 | printf '%s\n%s\n%s\n' "$1" "$1" "$2" | 48 | sort -k 3 | 49 | uniq -u -f 2 50 | } 51 | 52 | get_remote_branches () { 53 | name="$1" 54 | 55 | git for-each-ref refs/remotes/$name/\* | 56 | sed "s| refs/remotes/$name/| |" 57 | } 58 | 59 | fetch_from () { 60 | url="${1%=*}" 61 | pushurl="${1#*=}" 62 | name="$(url2remotename "$url")" 63 | 64 | if test "$url" != "$(git config remote.$name.url 2> /dev/null)" 65 | then 66 | git remote add $name $url >&2 || { 67 | add_error "Could not add remote $name ($url)" 68 | return 1 69 | } 70 | fi 71 | test -n "$pushurl" && 72 | test "$pushurl" != "$url" && 73 | git config remote.$name.pushURL "$pushurl" 74 | previous="$(get_remote_branches $name)" 75 | git fetch --prune $name >&2 || { 76 | add_error "Could not fetch $name" 77 | return 1 78 | } 79 | current="$(get_remote_branches $name)" 80 | 81 | find_deleted "$previous" "$current" 82 | 83 | # force modified branches 84 | find_modified "$previous" "$current" | 85 | sed 's/^/+/' 86 | 87 | find_new "$previous" "$current" 88 | } 89 | 90 | has_spaces () { 91 | test $# -gt 1 92 | } 93 | 94 | get_common_fast_forward () { 95 | test $# -le 1 && { 96 | echo "$*" 97 | return 98 | } 99 | head= 100 | while test $# -gt 0 101 | do 102 | commit=$1 103 | shift 104 | test -z "$(eval git rev-list --no-walk ^$commit $head $*)" && { 105 | echo $commit 106 | return 107 | } 108 | head="$head $commit" 109 | done 110 | echo $head 111 | } 112 | 113 | # Parameter check 114 | 115 | test $# -lt 2 && { 116 | echo "Usage: $0 [=] [=]..." >&2 117 | exit 1 118 | } 119 | 120 | test -d .git || 121 | git init || 122 | exit 123 | 124 | # Fetch 125 | 126 | todo= 127 | for urlpair 128 | do 129 | url="${urlpair%=*}" 130 | has_spaces $url && { 131 | add_error "Error: Ignoring URL with spaces: $url" 132 | continue 133 | } 134 | 135 | echo "Getting updates from $url..." 136 | thistodo="$(fetch_from $urlpair)" || { 137 | add_error "$thistodo" 138 | continue 139 | } 140 | test -z "$thistodo" && continue 141 | printf "Updates from $url:\n%s\n" "$thistodo" 142 | todo="$(printf "%s\n%s\n" "$todo" "$thistodo")" 143 | done 144 | 145 | remote_branches="$(for url 146 | do 147 | url="${url%=*}" 148 | has_spaces $url && continue 149 | name=$(url2remotename $url) 150 | git for-each-ref refs/remotes/$name/\* | 151 | sed "s|^\(.*\) refs/remotes/\($name\)/|\2 \1 |" 152 | done)" 153 | 154 | for ref in $(echo "$remote_branches" | 155 | sed 's/.* //' | 156 | sort | 157 | uniq) 158 | do 159 | echo "$todo" | grep " $ref$" > /dev/null 2>&1 && continue 160 | quoted_ref="$(echo "$ref" | sed 's/\./\\&/g')" 161 | sha1="$(echo "$remote_branches" | 162 | sed -n "s|^[^ ]* \([^ ]*\) [^ ]* $quoted_ref$|\1|p" | 163 | sort | 164 | uniq)" 165 | sha1=$(eval get_common_fast_forward $sha1) 166 | case "$sha1" in 167 | *\ *) 168 | add_error "$(printf "Ref $ref is diverging:\n%s\n\n" "$(echo "$remote_branches" | 169 | grep " $ref$")")" 170 | continue 171 | ;; 172 | *) 173 | 174 | if test $# = $(echo "$remote_branches" | 175 | grep "$sha1 [^ ]* $ref$" | 176 | wc -l) 177 | then 178 | # all refs agree on one sha1 179 | continue 180 | fi 181 | ;; 182 | esac 183 | echo "Need to fast-forward $ref to $sha1" 184 | todo="$(printf "%s\n%s\n" "$todo" "$sha1 commit $ref")" 185 | done 186 | 187 | # Verify 188 | 189 | # normalize todo 190 | 191 | todo="$(echo "$todo" | 192 | sort -k 3 | 193 | uniq | 194 | grep -v '^$')" 195 | 196 | # test for disagreeing updates 197 | 198 | refs=$(echo "$todo" | 199 | sed 's/^[^ ]* [^ ]* //' | 200 | sort | 201 | uniq -d) 202 | for ref in $refs 203 | do 204 | sha1=$(echo "$todo" | 205 | sed -n "s|^\([^ ]*\) [^ ]* $ref$|\1|p") 206 | sha1=$(get_common_fast_forward $sha1) 207 | has_spaces $sha1 || 208 | todo="$(echo "$todo" | 209 | sed "s|^[^ ]* \([^ ]* $ref\)$|$sha1 \1|" | 210 | uniq)" 211 | done 212 | 213 | disagreeing=$(echo "$todo" | 214 | cut -f 2 | 215 | sort | 216 | uniq -d) 217 | 218 | if test -n "$disagreeing" 219 | then 220 | message="$(for name in $disagreeing 221 | do 222 | echo "$todo" | grep " $name$" 223 | done)" 224 | add_error "$(printf "Incompatible updates:\n%s\n\n" "$message")" 225 | fi 226 | 227 | test -z "$todo" || git gc --auto 228 | 229 | # make it easier to test whether a name is in $disagreeing via: 230 | # test "$disagreeing" != "${disagreeing#* $name }" 231 | disagreeing=" $disagreeing " 232 | 233 | # Push 234 | 235 | test -z "$todo" || 236 | for url 237 | do 238 | url="${url%=*}" 239 | has_spaces $url && continue 240 | name="$(url2remotename $url)" 241 | pushopts=$(echo "$todo" | 242 | while read sha1 type ref 243 | do 244 | test -z "$sha1" && continue 245 | test "$disagreeing" = "${disagreeing#* $name }" || continue 246 | remoteref=refs/remotes/$name/$ref 247 | if test $sha1 = $nullsha1 248 | then 249 | # to delete 250 | if git rev-parse $remoteref > /dev/null 2>&1 251 | then 252 | echo ":refs/heads/$ref" 253 | fi 254 | else 255 | sha1=${sha1#+} 256 | if test $sha1 != "$(git rev-parse $remoteref 2> /dev/null)" 257 | then 258 | if test -n "$(git rev-list "$sha1..$remoteref")" 259 | then 260 | # really need to force 261 | echo "+$sha1:refs/heads/$ref" 262 | else 263 | echo "$sha1:refs/heads/$ref" 264 | fi 265 | fi 266 | fi 267 | done) 268 | test -z "$pushopts" && continue 269 | deletefirst= 270 | case "$(git config "remote.$name.pushurl"; git config "remote.$name.url")" in 271 | git://*) 272 | case "$pushopts" in 273 | *+*) 274 | add_error "Diverging $url: ${pushopts#*+}" 275 | ;; 276 | esac 277 | continue 278 | ;; 279 | *.sf.net*|*.sourceforge.net*) 280 | for opt in $pushopts 281 | do 282 | test "$opt" = "${opt#+*:}" || 283 | deletefirst="$deletefirst :${opt#+*:}" 284 | done 285 | esac 286 | test -z "$deletefirst" || 287 | git push $name $deletefirst || 288 | add_error "Could not push $deletefirst to $url" 289 | git push $name $pushopts || 290 | add_error "Could not push to $url" 291 | done 292 | 293 | # Maybe error out 294 | 295 | test -z "$errors" || { 296 | printf "\n\nErrors:\n%s\n" "$errors" >&2 297 | exit 1 298 | } 299 | -------------------------------------------------------------------------------- /github-actionify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # github-actionify.sh 4 | # 5 | # Script for enabling or updating GitHub Action builds for a given repository. 6 | 7 | #set -e 8 | 9 | dir="$(dirname "$0")" 10 | 11 | ciDir=.github 12 | ciSlugBuild=workflows/build.yml 13 | ciConfigBuild=$ciDir/$ciSlugBuild 14 | ciSetupScript=$ciDir/setup.sh 15 | ciBuildScript=$ciDir/build.sh 16 | pomMinVersion='17.1.1' 17 | tmpFile=github-actionify.tmp 18 | msgPrefix="CI: " 19 | 20 | info() { echo "- $@"; } 21 | warn() { echo "[WARNING] $@" 1>&2; } 22 | err() { echo "[ERROR] $@" 1>&2; } 23 | die() { err "$@"; exit 1; } 24 | 25 | check() { 26 | for tool in $@ 27 | do 28 | which "$tool" >/dev/null || 29 | die "The '$tool' utility is required but not found" 30 | done 31 | } 32 | 33 | update() { 34 | file=$1 35 | msg=$2 36 | exe=$3 37 | test "$msg" || msg="update $file" 38 | if [ -e "$file" ] 39 | then 40 | if diff -q "$file" "$tmpFile" >/dev/null 41 | then 42 | info "$file is already OK" 43 | else 44 | info "Updating $file" 45 | $EXEC rm -rf "$file" 46 | $EXEC mv -f "$tmpFile" "$file" 47 | fi 48 | else 49 | info "Creating $file" 50 | $EXEC mkdir -p "$(dirname "$file")" 51 | $EXEC mv "$tmpFile" "$file" 52 | fi 53 | rm -rf "$tmpFile" 54 | $EXEC git add "$file" 55 | if [ -n "$exe" ] 56 | then 57 | info "Adding execute permission to $file" 58 | $EXEC git update-index --chmod=+x "$file" 59 | fi 60 | $EXEC git diff-index --quiet HEAD -- || $EXEC git commit -m "$msgPrefix$msg" 61 | } 62 | 63 | process() { 64 | cd "$1" 65 | 66 | # -- Git sanity checks -- 67 | 68 | repoSlug=$(grep '' pom.xml | sed 's;.*github.com[/:]\(.*/.*\).*;\1;') 69 | test "$repoSlug" && info "Repository = $repoSlug" || die 'Could not determine GitHub repository slug' 70 | case "$repoSlug" in 71 | *.git) 72 | die "GitHub repository slug ('$repoSlug') ends in '.git'; please fix the POM" 73 | ;; 74 | esac 75 | git fetch >/dev/null 76 | git diff-index --quiet HEAD -- || die "Dirty working copy" 77 | currentBranch=$(git rev-parse --abbrev-ref HEAD) 78 | upstreamBranch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u}) 79 | remote=${upstreamBranch%/*} 80 | defaultBranch=$(git remote show "$remote" | grep "HEAD" | sed 's/.*: //') 81 | test "$currentBranch" = "$defaultBranch" || die "Non-default branch: $currentBranch" 82 | git merge --ff --ff-only 'HEAD@{u}' >/dev/null || 83 | die "Cannot fast forward (local diverging?)" 84 | # test "$(git rev-parse HEAD)" = "$(git rev-parse 'HEAD@{u}')" || 85 | # die "Mismatch with upstream branch (local ahead?)" 86 | 87 | # -- POM sanity checks -- 88 | 89 | parent=$(grep -A4 '' pom.xml | grep '' | sed 's;.*\(.*\).*;\1;') 90 | if [ -z "$SKIP_PARENT_CHECK" ] 91 | then 92 | test "$parent" = "pom-scijava" || 93 | die "Not pom-scijava parent: $parent. Run with -p flag to skip this check." 94 | fi 95 | 96 | # Change pom.xml from Travis CI to GitHub Actions 97 | domain="github.com" 98 | sed 's/Travis CI/GitHub Actions/g' pom.xml | 99 | sed "s;travis-ci.*;github.com/$repoSlug/actions;g" >"$tmpFile" 100 | update pom.xml "switch from Travis CI to GitHub Actions" 101 | 102 | # -- GitHub Action sanity checks -- 103 | 104 | test -e "$ciDir" -a ! -d "$ciDir" && die "$ciDir is not a directory" 105 | test -e "$ciConfigBuild" -a ! -f "$ciConfigBuild" && die "$ciConfigBuild is not a regular file" 106 | test -e "$ciConfigBuild" && warn "$ciConfigBuild already exists" 107 | test -e "$ciBuildScript" && warn "$ciBuildScript already exists" 108 | test -e "$ciSetupScript" && warn "$ciSetupScript already exists" 109 | 110 | # -- GitHub Action steps -- 111 | 112 | actionCheckout="uses: actions/checkout@v4" 113 | actionSetupJava="name: Set up Java 114 | uses: actions/setup-java@v4 115 | with: 116 | java-version: '8' 117 | distribution: 'zulu' 118 | cache: 'maven'" 119 | actionSetupConda="name: Set up conda 120 | uses: s-weigand/setup-conda@v1 121 | - name: Install conda packages 122 | run: conda env update -f environment.yml -n base" 123 | actionSetupCI="name: Set up CI environment 124 | run: $ciSetupScript 125 | shell: bash" 126 | actionExecuteBuild="name: Execute the build 127 | run: $ciBuildScript 128 | shell: bash" 129 | actionSecrets="env: 130 | GPG_KEY_NAME: \${{ secrets.GPG_KEY_NAME }} 131 | GPG_PASSPHRASE: \${{ secrets.GPG_PASSPHRASE }} 132 | MAVEN_USER: \${{ secrets.MAVEN_USER }} 133 | MAVEN_PASS: \${{ secrets.MAVEN_PASS }} 134 | CENTRAL_USER: \${{ secrets.CENTRAL_USER }} 135 | CENTRAL_PASS: \${{ secrets.CENTRAL_PASS }} 136 | SIGNING_ASC: \${{ secrets.SIGNING_ASC }}" 137 | 138 | # -- Do things -- 139 | 140 | # Add/update the GitHub Actions build configuration file. 141 | cat >"$tmpFile" <>"$tmpFile" 163 | cat >>"$tmpFile" <"$tmpFile" <"$tmpFile" <' pom.xml | grep '' | sed 's;.*\(.*\).*;\1;') 192 | # HACK: Using a lexicographic comparison here is imperfect. 193 | if [ "$version" \< "$pomMinVersion" ] 194 | then 195 | info 'Upgrading pom-scijava version' 196 | sed "s|^ $version$| $pomMinVersion|" pom.xml >"$tmpFile" 197 | update pom.xml "update pom-scijava parent to $pomMinVersion" 198 | else 199 | info "Version of pom-scijava ($version) is OK" 200 | fi 201 | fi 202 | 203 | # ensure section is present 204 | releaseProfile=$(grep '' pom.xml 2>/dev/null | sed 's/[^>]*>//' | sed 's/<.*//') 205 | if [ "$releaseProfile" ] 206 | then 207 | case "$releaseProfile" in 208 | sign,deploy-to-scijava) 209 | info 'No changes needed to property' 210 | ;; 211 | deploy-to-scijava) 212 | info 'Updating property' 213 | sed 's;\(\).*\(\);\1sign,deploy-to-scijava\2;' pom.xml >"$tmpFile" 214 | update pom.xml 'sign JARs when deploying releases' 215 | ;; 216 | *) 217 | warn "Unknown release profile: $releaseProfile" 218 | ;; 219 | esac 220 | else 221 | info 'Adding property' 222 | cp pom.xml "$tmpFile" 223 | perl -0777 -i -pe 's/(\n\t<\/properties>\n)/\n\n\t\t\n\t\tsign,deploy-to-scijava<\/releaseProfiles>\1/igs' "$tmpFile" 224 | update pom.xml 'deploy releases to the SciJava repository' 225 | fi 226 | 227 | # update the README 228 | # https://docs.github.com/en/actions/managing-workflow-runs/adding-a-workflow-status-badge 229 | if grep -q "travis-ci.*svg" README.md >/dev/null 2>&1 230 | then 231 | info "Updating README.md GitHub Action badge" 232 | sed "s;travis-ci.*;$domain/$repoSlug/actions/$ciSlugBuild/badge.svg)](https://$domain/$repoSlug/actions/$ciSlugBuild);g" README.md >"$tmpFile" 233 | update README.md 'update README.md badge link' 234 | elif grep -qF "$domain/$repoSlug/actions/$ciSlugBuild/badge.svg" README.md >/dev/null 2>&1 235 | then 236 | info "GitHub Action badge already present in README.md" 237 | else 238 | info "Adding GitHub Action badge to README.md" 239 | echo "[![Build Status](https://$domain/$repoSlug/actions/$ciSlugBuild/badge.svg)](https://$domain/$repoSlug/actions/$ciSlugBuild)" >"$tmpFile" 240 | echo >>"$tmpFile" 241 | test -f README.md && cat README.md >>"$tmpFile" 242 | update README.md 'add README.md badge link' 243 | fi 244 | 245 | # remove old Travis CI configuration 246 | test ! -e .travis.yml || $EXEC git rm -rf .travis.yml 247 | test ! -e .travis || $EXEC git rm -rf .travis 248 | $EXEC git diff-index --quiet HEAD -- && 249 | info "No old CI configuration to remove." || 250 | $EXEC git commit -m "${msgPrefix}remove Travis CI configuration" 251 | } 252 | 253 | cat <&2; break;; 278 | *) break;; 279 | esac 280 | shift 281 | done 282 | 283 | test "$EXEC" && warn "Simulation only. Run with -f flag to go for real." 284 | 285 | # process arguments 286 | if [ $# -gt 0 ] 287 | then 288 | for d in $@ 289 | do ( 290 | echo "[$d]" 291 | process "$d" 292 | ) done 293 | else 294 | process . 295 | fi 296 | -------------------------------------------------------------------------------- /ci-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # ci-build.sh - A script to build and/or release SciJava-based projects 5 | # automatically using a continuous integration service. 6 | # 7 | # Optional environment variables: 8 | # BUILD_REPOSITORY - the repository URL running the current build 9 | 10 | dir="$(dirname "$0")" 11 | 12 | platform=$(uname -s) 13 | 14 | success=0 15 | checkSuccess() { 16 | # Log non-zero exit code. 17 | test $1 -eq 0 || echo "==> FAILED: EXIT CODE $1" 1>&2 18 | 19 | if [ $1 -ne 0 -a -f "$2" ] 20 | then 21 | # The operation failed and a log file was provided. 22 | # Do some heuristics, because we like being helpful! 23 | javadocErrors=$(grep error: "$2") 24 | generalErrors=$(grep -i '\b\(errors\?\|fail\|failures\?\)\b' "$2") 25 | if [ "$javadocErrors" ] 26 | then 27 | echo 28 | echo '/----------------------------------------------------------\' 29 | echo '| ci-build.sh analysis: I noticed probable javadoc errors: |' 30 | echo '\----------------------------------------------------------/' 31 | echo "$javadocErrors" 32 | elif [ "$generalErrors" ] 33 | then 34 | echo 35 | echo '/-------------------------------------------------------\' 36 | echo '| ci-build.sh analysis: I noticed the following errors: |' 37 | echo '\-------------------------------------------------------/' 38 | echo "$generalErrors" 39 | else 40 | echo 41 | echo '/----------------------------------------------------------------------\' 42 | echo '| ci-build.sh analysis: I see no problems in the operation log. Sorry! |' 43 | echo '\----------------------------------------------------------------------/' 44 | echo 45 | fi 46 | fi 47 | 48 | # Record the first non-zero exit code. 49 | test $success -eq 0 && success=$1 50 | } 51 | 52 | # Credit: https://stackoverflow.com/a/12873723/1207769 53 | escapeXML() { 54 | echo "$1" | sed 's/&/\&/g; s//\>/g; s/"/\"/g; s/'"'"'/\'/g' 55 | } 56 | 57 | mavenEvaluate() { 58 | mvn -B -U -q -Denforcer.skip=true -Dexec.executable=echo -Dexec.args="$1" --non-recursive validate exec:exec 2>&1 59 | } 60 | 61 | # Output debugging info, for troubleshooting builds. 62 | dump() { 63 | if env | grep -q ^$1= 64 | then 65 | # Environment variable is set. 66 | line=$(env | grep ^$1=) 67 | if [ "$line" ] 68 | then 69 | # And it's non-empty. 70 | if [ "$2" ] 71 | then 72 | # Secret value: emit only that it is present. 73 | echo "$1=" 74 | else 75 | # Not a secret: emit in plaintext. 76 | echo "$line" 77 | fi 78 | else 79 | # But it is empty! 80 | echo "$1=" 81 | fi 82 | else 83 | # Environment variable is not set. 84 | echo "$1=" 85 | fi 86 | } 87 | 88 | analyzeGitHubWorkflow() { 89 | test -d .github/workflows || return 90 | echo 91 | echo '/--------------------------------------------------\' 92 | echo '| ci-build.sh analysis: env vars + GitHub workflow |' 93 | echo '\--------------------------------------------------/' 94 | dump dir 95 | dump platform 96 | dump BUILD_REPOSITORY 97 | dump NO_DEPLOY 98 | echo '----------------------------------------------------' 99 | dump GPG_KEY_NAME secret 100 | dump GPG_PASSPHRASE secret 101 | dump MAVEN_USER secret 102 | dump MAVEN_PASS secret 103 | dump CENTRAL_USER secret 104 | dump CENTRAL_PASS secret 105 | dump SIGNING_ASC secret 106 | echo '----------------------------------------------------' 107 | for var in \ 108 | GPG_KEY_NAME \ 109 | GPG_PASSPHRASE \ 110 | MAVEN_USER \ 111 | MAVEN_PASS \ 112 | CENTRAL_USER \ 113 | CENTRAL_PASS \ 114 | SIGNING_ASC 115 | do 116 | grep -q "secrets.$var" .github/workflows/*.yml || 117 | echo "Add \`$var: \${{ secrets.$var }}\` to GitHub workflow env section!" 118 | done 119 | } 120 | 121 | # Build Maven projects. 122 | if [ -f pom.xml ]; then 123 | echo ::group::"= Maven build =" 124 | 125 | # --== MAVEN SETUP ==-- 126 | 127 | echo 128 | echo '== Configuring Maven ==' 129 | 130 | # NB: Suppress "Downloading/Downloaded" messages. 131 | # See: https://stackoverflow.com/a/35653426/1207769 132 | export MAVEN_OPTS="$MAVEN_OPTS -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" 133 | 134 | # Populate the settings.xml configuration. 135 | mkdir -p "$HOME/.m2" 136 | settingsFile="$HOME/.m2/settings.xml" 137 | customSettings=.ci/settings.xml 138 | if [ "$OSSRH_USER" -o "$OSSRH_PASS" ]; then 139 | echo '[WARNING] Obsolete OSSRH vars detected. Secrets may need updating to deploy to Maven Central.' 140 | fi 141 | if [ -f "$customSettings" ]; then 142 | cp "$customSettings" "$settingsFile" 143 | elif [ -z "$BUILD_REPOSITORY" ]; then 144 | echo 'Skipping settings.xml generation (no BUILD_REPOSITORY; assuming we are running locally)' 145 | else 146 | # settings.xml header 147 | cat >"$settingsFile" < 149 | 150 | EOL 151 | # settings.xml scijava servers 152 | if [ "$MAVEN_USER" -a "$MAVEN_PASS" ]; then 153 | cat >>"$settingsFile" < 155 | scijava.releases 156 | $MAVEN_USER 157 | $(escapeXML "$MAVEN_PASS") 158 | 159 | 160 | scijava.snapshots 161 | $MAVEN_USER 162 | $(escapeXML "$MAVEN_PASS") 163 | 164 | EOL 165 | else 166 | echo '[WARNING] Skipping settings.xml scijava servers (no MAVEN deployment credentials).' 167 | fi 168 | # settings.xml central server 169 | if [ "$CENTRAL_USER" -a "$CENTRAL_PASS" ]; then 170 | cat >>"$settingsFile" < 172 | central 173 | $CENTRAL_USER 174 | $(escapeXML "$CENTRAL_PASS") 175 | 176 | EOL 177 | else 178 | echo '[WARNING] Skipping settings.xml central server (no CENTRAL deployment credentials).' 179 | fi 180 | cat >>"$settingsFile" < 182 | EOL 183 | # settings.xml GPG profile 184 | if [ "$GPG_KEY_NAME" -a "$GPG_PASSPHRASE" ]; then 185 | cat >>"$settingsFile" < 187 | 188 | gpg 189 | 190 | 191 | $HOME/.gnupg 192 | 193 | 194 | 195 | $GPG_KEY_NAME 196 | $(escapeXML "$GPG_PASSPHRASE") 197 | 198 | 199 | 200 | EOL 201 | else 202 | echo '[WARNING] Skipping settings.xml gpg profile (no GPG credentials).' 203 | fi 204 | # settings.xml footer 205 | cat >>"$settingsFile" < 207 | EOL 208 | fi 209 | 210 | # --== DEPLOYMENT CHECKS ==-- 211 | 212 | # Determine whether deploying is both possible and warranted. 213 | echo 'Performing deployment checks' 214 | deployOK= 215 | 216 | scmURL=$(mavenEvaluate '${project.scm.url}') 217 | result=$? 218 | checkSuccess $result 219 | if [ $result -ne 0 ]; then 220 | echo 'No deploy -- could not extract ciManagement URL' 221 | echo 'Output of failed attempt follows:' 222 | echo "$scmURL" 223 | else 224 | scmURL=${scmURL%.git} 225 | scmURL=${scmURL%/} 226 | if [ "$NO_DEPLOY" ]; then 227 | echo 'No deploy -- the NO_DEPLOY flag is set' 228 | elif [ "$BUILD_REPOSITORY" -a "$BUILD_REPOSITORY" != "$scmURL" ]; then 229 | echo "No deploy -- repository fork: $BUILD_REPOSITORY != $scmURL" 230 | elif [ "$BUILD_BASE_REF" -o "$BUILD_HEAD_REF" ]; then 231 | echo "No deploy -- proposed change: $BUILD_HEAD_REF -> $BUILD_BASE_REF" 232 | else 233 | # Are we building a snapshot version, or a release version? 234 | version=$(mavenEvaluate '${project.version}') 235 | result=$? 236 | checkSuccess $result 237 | if [ $result -ne 0 ]; then 238 | echo 'No deploy -- could not extract version string' 239 | echo 'Output of failed attempt follows:' 240 | echo "$version" 241 | else 242 | case "$version" in 243 | *-SNAPSHOT) 244 | # Snapshot version -- ensure release.properties not present. 245 | if [ -f release.properties ]; then 246 | echo '[ERROR] Spurious release.properties file is present' 247 | echo 'Remove the file from version control and try again.' 248 | exit 1 249 | fi 250 | 251 | # Check for SciJava Maven repository credentials. 252 | if [ "$MAVEN_USER" -a "$MAVEN_PASS" ]; then 253 | deployOK=1 254 | else 255 | echo 'No deploy -- MAVEN environment variables not available' 256 | analyzeGitHubWorkflow 257 | fi 258 | ;; 259 | *) 260 | # Release version -- ensure release.properties is present. 261 | if [ ! -f release.properties ]; then 262 | echo '[ERROR] Release version, but release.properties not found' 263 | echo 'You must use release-version.sh to release -- see https://imagej.net/develop/releasing' 264 | exit 1 265 | fi 266 | 267 | # To which repository are we releasing? 268 | releaseProfiles=$(mavenEvaluate '${releaseProfiles}') 269 | result=$? 270 | checkSuccess $result 271 | if [ $result -ne 0 ]; then 272 | echo 'No deploy -- could not extract releaseProfiles string' 273 | echo 'Output of failed attempt follows:' 274 | echo "$releaseProfiles" 275 | fi 276 | case "$releaseProfiles" in 277 | *deploy-to-scijava*) 278 | # Check for SciJava Maven repository credentials. 279 | if [ "$MAVEN_USER" -a "$MAVEN_PASS" ]; then 280 | deployOK=1 281 | else 282 | echo '[ERROR] Cannot deploy: MAVEN environment variables not available' 283 | analyzeGitHubWorkflow 284 | exit 1 285 | fi 286 | ;; 287 | *sonatype-oss-release*) 288 | # Check for Central Portal deployment credentials. 289 | # Deploy to Central requires GPG-signed artifacts. 290 | if [ "$CENTRAL_USER" -a "$CENTRAL_PASS" -a "$SIGNING_ASC" -a "$GPG_KEY_NAME" -a "$GPG_PASSPHRASE" ]; then 291 | deployOK=1 292 | else 293 | echo '[ERROR] Cannot deploy: CENTRAL environment variables not available' 294 | analyzeGitHubWorkflow 295 | exit 1 296 | fi 297 | ;; 298 | *) 299 | echo 'Unknown deploy target -- attempting to deploy anyway' 300 | deployOK=1 301 | ;; 302 | esac 303 | ;; 304 | esac 305 | fi 306 | fi 307 | fi 308 | if [ "$deployOK" ]; then 309 | echo 'All checks passed for artifact deployment' 310 | fi 311 | 312 | # --== Maven build arguments ==-- 313 | 314 | BUILD_ARGS="$BUILD_ARGS -B -Djdk.tls.client.protocols=TLSv1,TLSv1.1,TLSv1.2" 315 | 316 | # --== GPG SETUP ==-- 317 | 318 | if [ "$GPG_KEY_NAME" -a "$GPG_PASSPHRASE" ]; then 319 | # Install GPG on macOS 320 | if [ "$platform" = Darwin ]; then 321 | HOMEBREW_NO_AUTO_UPDATE=1 brew install gnupg2 322 | fi 323 | 324 | # Avoid "signing failed: Inappropriate ioctl for device" error. 325 | export GPG_TTY=$(tty) 326 | 327 | # Import the GPG signing key. 328 | keyFile=.ci/signingkey.asc 329 | if [ "$deployOK" ]; then 330 | echo '== Importing GPG keypair ==' 331 | mkdir -p .ci 332 | echo "$SIGNING_ASC" > "$keyFile" 333 | ls -la "$keyFile" 334 | gpg --version 335 | gpg --batch --fast-import "$keyFile" 336 | checkSuccess $? 337 | fi 338 | 339 | # HACK: Use maven-gpg-plugin 3.0.1+. Avoids "signing failed: No such file or directory" error. 340 | maven_gpg_plugin_version=$(mavenEvaluate '${maven-gpg-plugin.version}') 341 | case "$maven_gpg_plugin_version" in 342 | 0.*|1.*|2.*|3.0.0) 343 | echo "--> Forcing maven-gpg-plugin version from $maven_gpg_plugin_version to 3.0.1" 344 | BUILD_ARGS="$BUILD_ARGS -Dmaven-gpg-plugin.version=3.0.1 -Darguments=-Dmaven-gpg-plugin.version=3.0.1" 345 | ;; 346 | *) 347 | echo "--> maven-gpg-plugin version OK: $maven_gpg_plugin_version" 348 | ;; 349 | esac 350 | 351 | # HACK: Install pinentry helper program if missing. Avoids "signing failed: No pinentry" error. 352 | if ! which pinentry >/dev/null 2>&1; then 353 | echo '--> Installing missing pinentry helper for GPG' 354 | sudo apt-get install -y pinentry-tty 355 | # HACK: Restart the gpg agent, to notice the newly installed pinentry. 356 | if { pgrep gpg-agent >/dev/null && which gpgconf >/dev/null 2>&1; } then 357 | echo '--> Restarting gpg-agent' 358 | gpgconf --reload gpg-agent 359 | checkSuccess $? 360 | fi 361 | fi 362 | else 363 | echo '[WARNING] Skipping gpg setup (no GPG credentials).' 364 | fi 365 | 366 | # --== BUILD EXECUTION ==-- 367 | 368 | # Run the build. 369 | if [ "$deployOK" -a -f release.properties ]; then 370 | echo 371 | echo '== Cutting and deploying release version ==' 372 | BUILD_ARGS="$BUILD_ARGS release:perform" 373 | elif [ "$deployOK" ]; then 374 | echo 375 | echo '== Building and deploying main branch SNAPSHOT ==' 376 | BUILD_ARGS="-Pdeploy-to-scijava $BUILD_ARGS deploy" 377 | else 378 | echo 379 | echo '== Building the artifact locally only ==' 380 | BUILD_ARGS="$BUILD_ARGS install javadoc:javadoc" 381 | fi 382 | # Check the build result. 383 | { (set -x; mvn $BUILD_ARGS); echo $? > exit-code; } | tee mvn-log 384 | checkSuccess "$(cat exit-code)" mvn-log 385 | 386 | # --== POST-BUILD ACTIONS ==-- 387 | 388 | # Dump logs for any failing unit tests. 389 | if [ -d target/surefire-reports ] 390 | then 391 | find target/surefire-reports -name '*.txt' | while read report 392 | do 393 | if grep -qF 'FAILURE!' "$report" 394 | then 395 | echo 396 | echo "[$report]" 397 | cat "$report" 398 | fi 399 | done 400 | fi 401 | 402 | echo ::endgroup:: 403 | fi 404 | 405 | # Execute Jupyter notebooks. 406 | if which jupyter >/dev/null 2>&1; then 407 | echo ::group::"= Jupyter notebooks =" 408 | # NB: This part is fiddly. We want to loop over files even with spaces, 409 | # so we use the "find ... | while read ..." idiom. 410 | # However, that runs the piped expression in a subshell, which means 411 | # that any updates to the success variable will not persist outside 412 | # the loop. So we store non-zero success values into a temporary file, 413 | # then capture the value back into the parent shell's success variable. 414 | find . -name '*.ipynb' | while read nbf 415 | do 416 | echo 417 | echo "== $nbf ==" 418 | jupyter nbconvert --to python --stdout --execute "$nbf" 419 | checkSuccess $? 420 | test "$success" -eq 0 || echo "$success" > success.tmp 421 | done 422 | test -f success.tmp && success=$(cat success.tmp) && rm success.tmp 423 | echo ::endgroup:: 424 | fi 425 | 426 | exit $success 427 | -------------------------------------------------------------------------------- /release-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ============================================================================ 4 | # release-version.sh 5 | # ============================================================================ 6 | # Releases a new version of a component extending the pom-scijava parent. 7 | # 8 | # Authors: Johannes Schindelin & Curtis Rueden 9 | # ============================================================================ 10 | 11 | # -- Avoid localized output that might confuse the script -- 12 | 13 | export LC_ALL=C 14 | 15 | # -- Functions -- 16 | 17 | debug() { 18 | test "$DEBUG" || return 19 | echo "[DEBUG] $@" 20 | } 21 | 22 | die() { 23 | echo "$*" >&2 24 | exit 1 25 | } 26 | 27 | no_changes_pending() { 28 | git update-index -q --refresh && 29 | git diff-files --quiet --ignore-submodules && 30 | git diff-index --cached --quiet --ignore-submodules HEAD -- 31 | } 32 | 33 | # -- Constants and settings -- 34 | 35 | SCIJAVA_BASE_REPOSITORY=-DaltDeploymentRepository=scijava.releases::default::dav:https://maven.scijava.org/content/repositories 36 | SCIJAVA_RELEASES_REPOSITORY=$SCIJAVA_BASE_REPOSITORY/releases 37 | SCIJAVA_THIRDPARTY_REPOSITORY=$SCIJAVA_BASE_REPOSITORY/thirdparty 38 | 39 | # Parse command line options. 40 | BATCH_MODE=--batch-mode 41 | SKIP_VERSION_CHECK= 42 | SKIP_BRANCH_CHECK= 43 | SKIP_LICENSE_UPDATE= 44 | SKIP_PUSH= 45 | SKIP_GPG= 46 | TAG= 47 | DEV_VERSION= 48 | EXTRA_ARGS= 49 | ALT_REPOSITORY= 50 | PROFILE=-Pdeploy-to-scijava 51 | DRY_RUN= 52 | USAGE= 53 | VERSION= 54 | while test $# -gt 0 55 | do 56 | case "$1" in 57 | --dry-run) DRY_RUN=echo;; 58 | --no-batch-mode) BATCH_MODE=;; 59 | --skip-version-check) SKIP_VERSION_CHECK=t;; 60 | --skip-branch-check) SKIP_BRANCH_CHECK=t;; 61 | --skip-license-update) SKIP_LICENSE_UPDATE=t;; 62 | --skip-push) SKIP_PUSH=t;; 63 | --tag=*) 64 | ! git rev-parse --quiet --verify refs/tags/"${1#--*=}" || 65 | die "Tag ${1#--*=} exists already!" 66 | TAG="-Dtag=${1#--*=}";; 67 | --dev-version=*|--development-version=*) 68 | DEV_VERSION="-DdevelopmentVersion=${1#--*=}";; 69 | --extra-arg=*|--extra-args=*) 70 | EXTRA_ARGS="$EXTRA_ARGS ${1#--*=}";; 71 | --alt-repository=scijava-releases) 72 | ALT_REPOSITORY=$SCIJAVA_RELEASES_REPOSITORY;; 73 | --alt-repository=scijava-thirdparty) 74 | ALT_REPOSITORY=$SCIJAVA_THIRDPARTY_REPOSITORY;; 75 | --alt-repository=*|--alt-deployment-repository=*) 76 | ALT_REPOSITORY="${1#--*=}";; 77 | --skip-gpg) 78 | SKIP_GPG=t 79 | EXTRA_ARGS="$EXTRA_ARGS -Dgpg.skip=true";; 80 | --help) 81 | USAGE=t 82 | break;; 83 | -*) 84 | echo "Unknown option: $1" >&2 85 | USAGE=t 86 | break;; 87 | *) 88 | test -z "$VERSION" || { 89 | echo "Extraneous argument: $1" >&2 90 | USAGE=t 91 | break 92 | } 93 | VERSION=$1;; 94 | esac 95 | shift 96 | done 97 | 98 | test "$USAGE" && 99 | die "Usage: $0 [options] [] 100 | 101 | Where is the version to release. If omitted, it will prompt you. 102 | 103 | Options include: 104 | --dry-run - Simulate the release without actually doing it. 105 | --skip-version-check - Skips the SemVer and parent pom version checks. 106 | --skip-branch-check - Skips the default branch check. 107 | --skip-license-update - Skips update of the copyright blurbs. 108 | --skip-push - Do not push to the remote git repository. 109 | --dev-version= - Specify next development version explicitly; 110 | e.g.: if you release 2.0.0-beta-1, by default 111 | Maven will set the next development version at 112 | 2.0.0-beta-2-SNAPSHOT, but maybe you want to 113 | set it to 2.0.0-SNAPSHOT instead. 114 | --alt-repository= - Deploy release to a different remote repository. 115 | --skip-gpg - Do not perform GPG signing of artifacts. 116 | " 117 | 118 | # -- Extract project details -- 119 | debug "Extracting project details" 120 | 121 | echoArg='${project.version}:${license.licenseName}:${project.parent.groupId}:${project.parent.artifactId}:${project.parent.version}' 122 | projectDetails=$(mvn -B -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) 123 | test $? -eq 0 || projectDetails=$(mvn -B -U -N -Dexec.executable=echo -Dexec.args="$echoArg" exec:exec -q) 124 | test $? -eq 0 || die "Could not extract version from pom.xml. Error follows:\n$projectDetails" 125 | printf '%s' "$projectDetails\n" | grep -Fqv '[ERROR]' || 126 | die "Error extracting version from pom.xml. Error follows:\n$projectDetails" 127 | # HACK: Even with -B, some versions of mvn taint the output with the [0m 128 | # color reset sequence. So we forcibly remove such sequences, just to be safe. 129 | projectDetails=$(printf '%s' "$projectDetails" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g") 130 | # And also remove extraneous newlines, particularly any trailing ones. 131 | projectDetails=$(printf '%s' "$projectDetails" | tr -d '\n') 132 | currentVersion=${projectDetails%%:*} 133 | projectDetails=${projectDetails#*:} 134 | licenseName=${projectDetails%%:*} 135 | parentGAV=${projectDetails#*:} 136 | 137 | # -- Sanity checks -- 138 | debug "Performing sanity checks" 139 | 140 | # Check that we have push rights to the repository. 141 | if [ ! "$SKIP_PUSH" ] 142 | then 143 | debug "Checking repository push rights" 144 | push=$(git remote -v | grep origin | grep '(push)') 145 | test "$push" || die 'No push URL found for remote origin. 146 | Please use "git remote -v" to double check your remote settings.' 147 | echo "$push" | grep -q 'git:/' && die 'Remote origin is read-only. 148 | Please use "git remote set-url origin ..." to change it.' 149 | fi 150 | 151 | # Discern the version to release. 152 | debug "Gleaning release version" 153 | pomVersion=${currentVersion%-SNAPSHOT} 154 | test "$VERSION" -o ! -t 0 || { 155 | printf 'Version? [%s]: ' "$pomVersion" 156 | read VERSION 157 | test "$VERSION" || VERSION=$pomVersion 158 | } 159 | 160 | # Check that the release version number starts with a digit. 161 | test "$VERSION" || die 'Please specify the version to release!' 162 | test "$SKIP_VERSION_CHECK" || { 163 | case "$VERSION" in 164 | [0-9]*) 165 | ;; 166 | *) 167 | die "Version '$VERSION' does not start with a digit! 168 | If you are sure, try again with --skip-version-check flag." 169 | esac 170 | } 171 | 172 | # Check that the release version number conforms to SemVer. 173 | VALID_SEMVER_BUMP="$(cd "$(dirname "$0")" && pwd)/valid-semver-bump.sh" 174 | test -f "$VALID_SEMVER_BUMP" || 175 | die "Missing helper script at '$VALID_SEMVER_BUMP' 176 | Do you have a full clone of https://github.com/scijava/scijava-scripts?" 177 | test "$SKIP_VERSION_CHECK" || { 178 | debug "Checking conformance to SemVer" 179 | sh -$- "$VALID_SEMVER_BUMP" "$pomVersion" "$VERSION" || 180 | die "If you are sure, try again with --skip-version-check flag." 181 | } 182 | 183 | # Check that the project extends the latest version of pom-scijava. 184 | test "$SKIP_VERSION_CHECK" -o "$parentGAV" != "${parentGAV#$}" || { 185 | debug "Checking pom-scijava parent version" 186 | psjMavenMetadata=https://repo1.maven.org/maven2/org/scijava/pom-scijava/maven-metadata.xml 187 | latestParentVersion=$(curl -fsL "$psjMavenMetadata" | grep '' | sed 's;.*>\([^<]*\)<.*;\1;') 188 | currentParentVersion=${parentGAV##*:} 189 | test "$currentParentVersion" = "$latestParentVersion" || 190 | die "Newer version of parent '$parentGAV' is available: $latestParentVersion. 191 | I recommend you update it before releasing. 192 | Or if you know better, try again with --skip-version-check flag." 193 | } 194 | 195 | # Check that the working copy is clean. 196 | debug "Checking if working copy is clean" 197 | no_changes_pending || die 'There are uncommitted changes!' 198 | test -z "$(git ls-files -o --exclude-standard)" || 199 | die 'There are untracked files! Please stash them before releasing.' 200 | 201 | # Discern default branch. 202 | debug "Discerning default branch" 203 | currentBranch=$(git rev-parse --abbrev-ref --symbolic-full-name HEAD) 204 | upstreamBranch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u}) 205 | remote=${upstreamBranch%/*} 206 | defaultBranch=$(git remote show "$remote" | grep "HEAD branch" | sed 's/.*: //') 207 | 208 | # Check that we are on the main branch. 209 | test "$SKIP_BRANCH_CHECK" || { 210 | debug "Checking current branch" 211 | test "$currentBranch" = "$defaultBranch" || die "Non-default branch: $currentBranch. 212 | If you are certain you want to release from this branch, 213 | try again with --skip-branch-check flag." 214 | } 215 | 216 | # If REMOTE is unset, use branch's upstream remote by default. 217 | REMOTE="${REMOTE:-$remote}" 218 | 219 | # Check that the main branch isn't behind the upstream branch. 220 | debug "Ensuring local branch is up-to-date" 221 | HEAD="$(git rev-parse HEAD)" && 222 | git fetch "$REMOTE" "$defaultBranch" && 223 | FETCH_HEAD="$(git rev-parse FETCH_HEAD)" && 224 | test "$FETCH_HEAD" = HEAD || 225 | test "$FETCH_HEAD" = "$(git merge-base $FETCH_HEAD $HEAD)" || 226 | die "'$defaultBranch' is not up-to-date" 227 | 228 | # Check for release-only files committed to the main branch. 229 | debug "Checking for spurious release-only files" 230 | for release_file in release.properties pom.xml.releaseBackup 231 | do 232 | if [ -e "$release_file" ] 233 | then 234 | echo "==========================================================================" 235 | echo "NOTE: $release_file was committed to source control. Removing now." 236 | echo "==========================================================================" 237 | git rm -rf "$release_file" && 238 | git commit "$release_file" \ 239 | -m 'Remove $release_file' \ 240 | -m 'It should only exist on release tags.' 241 | fi 242 | done 243 | 244 | # Ensure that schema location URL uses HTTPS, not HTTP. 245 | debug "Checking that schema location URL uses HTTPS" 246 | if grep -q http://maven.apache.org/xsd/maven-4.0.0.xsd pom.xml >/dev/null 2>/dev/null 247 | then 248 | echo "=====================================================================" 249 | echo "NOTE: Your POM's schema location uses HTTP, not HTTPS. Fixing it now." 250 | echo "=====================================================================" 251 | sed 's;http://maven.apache.org/xsd/maven-4.0.0.xsd;https://maven.apache.org/xsd/maven-4.0.0.xsd;' pom.xml > pom.new && 252 | mv -f pom.new pom.xml && 253 | git commit pom.xml -m 'POM: use HTTPS for schema location URL' \ 254 | -m 'Maven no longer supports plain HTTP for the schema location.' \ 255 | -m 'And using HTTP now generates errors in Eclipse (and probably other IDEs).' 256 | fi 257 | 258 | # Check project xmlns, xmlns:xsi, and xsi:schemaLocation attributes. 259 | debug "Checking correctness of POM project XML attributes" 260 | grep -qF 'xmlns="http://maven.apache.org/POM/4.0.0"' pom.xml >/dev/null 2>/dev/null && 261 | grep -qF 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' pom.xml >/dev/null 2>/dev/null && 262 | grep -qF 'xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 ' pom.xml >/dev/null 2>/dev/null || 263 | { 264 | echo "=====================================================================" 265 | echo "NOTE: Your POM's project attributes are incorrect. Fixing it now." 266 | echo "=====================================================================" 267 | sed 's;xmlns="[^"]*";xmlns="http://maven.apache.org/POM/4.0.0";' pom.xml > pom.new && 268 | mv -f pom.new pom.xml && 269 | sed 's;xmlns:xsi="[^"]*";xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";' pom.xml > pom.new && 270 | mv -f pom.new pom.xml && 271 | sed 's;xsi:schemaLocation="[^"]*";xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd";' pom.xml > pom.new && 272 | mv -f pom.new pom.xml && 273 | git commit pom.xml -m 'POM: fix project attributes' \ 274 | -m 'The XML schema for Maven POMs is located at:' \ 275 | -m ' https://maven.apache.org/xsd/maven-4.0.0.xsd' \ 276 | -m 'Its XML namespace is the string:' \ 277 | -m ' http://maven.apache.org/POM/4.0.0' \ 278 | -m 'So that exact string must be the value of xmlns. It must also 279 | match the first half of xsi:schemaLocation, which maps that 280 | namespace to an actual URL online where the schema resides. 281 | Otherwise, the document is not a Maven POM.' \ 282 | -m 'Similarly, the xmlns:xsi attribute of an XML document declaring a 283 | particular schema should always use the string identifier:' \ 284 | -m ' http://www.w3.org/2001/XMLSchema-instance' \ 285 | -m "because that's the namespace identifier for instances of an XML schema." \ 286 | -m "For details, see the specification at: https://www.w3.org/TR/xmlschema-1/" 287 | } 288 | 289 | # Change forum references from forum.image.net to forum.image.sc. 290 | debug "Checking correctness of forum URL references" 291 | if grep -q 'https*://forum.imagej.net' pom.xml >/dev/null 2>/dev/null 292 | then 293 | echo "================================================================" 294 | echo "NOTE: Your POM still references forum.imagej.net. Fixing it now." 295 | echo "================================================================" 296 | sed 's;https*://forum.imagej.net;https://forum.image.sc;g' pom.xml > pom.new && 297 | mv -f pom.new pom.xml && 298 | git commit pom.xml \ 299 | -m 'POM: fix forum.image.sc tag link' \ 300 | -m 'The Discourse software updated the tags path from /tags/ to /tag/.' 301 | fi 302 | 303 | # Ensure that references to forum.image.sc use /tag/, not /tags/. 304 | debug "Checking correctness of forum tag references" 305 | if grep -q forum.image.sc/tags/ pom.xml >/dev/null 2>/dev/null 306 | then 307 | echo "==================================================================" 308 | echo "NOTE: Your POM has an old-style forum.image.sc tag. Fixing it now." 309 | echo "==================================================================" 310 | sed 's;forum.image.sc/tags/;forum.image.sc/tag/;g' pom.xml > pom.new && 311 | mv -f pom.new pom.xml && 312 | git commit pom.xml \ 313 | -m 'POM: fix forum.image.sc tag link' \ 314 | -m 'The Discourse software updated the tags path from /tags/ to /tag/.' 315 | fi 316 | 317 | # Ensure license headers are up-to-date. 318 | test "$SKIP_LICENSE_UPDATE" -o -z "$licenseName" -o "$licenseName" = "N/A" || { 319 | debug "Ensuring that license headers are up-to-date" 320 | mvn license:update-project-license license:update-file-header && 321 | git add LICENSE.txt || die 'Failed to update copyright blurbs. 322 | You can skip the license update using the --skip-license-update flag.' 323 | no_changes_pending || 324 | die 'Copyright blurbs needed an update -- commit changes and try again. 325 | Or if the license headers are being added erroneously to certain files, 326 | exclude them by setting license.excludes in your POM; e.g.: 327 | 328 | **/script_templates/** 329 | 330 | Alternately, try again with the --skip-license-update flag.' 331 | } 332 | 333 | # Prepare new release without pushing (requires the release plugin >= 2.1). 334 | debug "Preparing new release" 335 | $DRY_RUN mvn $BATCH_MODE release:prepare -DpushChanges=false -Dresume=false $TAG \ 336 | $PROFILE $DEV_VERSION -DreleaseVersion="$VERSION" \ 337 | "-Darguments=-Dgpg.skip=true ${EXTRA_ARGS# }" || 338 | die 'The release preparation step failed -- look above for errors and fix them. 339 | Use "mvn javadoc:javadoc | grep error" to check for javadoc syntax errors.' 340 | 341 | # Squash the maven-release-plugin's two commits into one. 342 | if test -z "$DRY_RUN" 343 | then 344 | debug "Squashing release commits" 345 | test "[maven-release-plugin] prepare for next development iteration" = \ 346 | "$(git show -s --format=%s HEAD)" || 347 | die "maven-release-plugin's commits are unexpectedly missing!" 348 | fi 349 | $DRY_RUN git reset --soft HEAD^^ && 350 | if ! git diff-index --cached --quiet --ignore-submodules HEAD -- 351 | then 352 | $DRY_RUN git commit -s -m "Bump to next development cycle" 353 | fi && 354 | 355 | # Check that release-only files were not erroneously committed. 356 | if test -z "$DRY_RUN" 357 | then 358 | debug "Verifying release-only files were not committed" 359 | for release_file in release.properties pom.xml.releaseBackup 360 | do 361 | if git diff --name-only HEAD~1 HEAD | grep -qF "$release_file" 362 | then 363 | die "FATAL: $release_file was committed to the branch! 364 | This likely means your IDE automatically staged these files. 365 | Please configure your IDE to NOT auto-stage files during the release process. 366 | You can undo this failed release with: 367 | git reset --hard HEAD~1 368 | git tag -d \$(sed -n 's/^scm.tag=//p' < release.properties) 369 | Then try the release again after disabling IDE auto-staging." 370 | fi 371 | done 372 | fi && 373 | 374 | # Extract the name of the new tag. 375 | debug "Extracting new tag name" 376 | if test -z "$DRY_RUN" 377 | then 378 | tag=$(sed -n 's/^scm.tag=//p' < release.properties) 379 | else 380 | tag="" 381 | fi && 382 | 383 | # Rewrite the tag to include release.properties. 384 | debug "Rewriting tag to include release.properties" 385 | test -n "$tag" && 386 | # HACK: SciJava projects use SSH (git@github.com:...) for developerConnection. 387 | # The release:perform command wants to use the developerConnection URL when 388 | # checking out the release tag. But reading from this URL requires credentials 389 | # which the CI system typically does not have. So we replace the scm.url in 390 | # the release.properties file to use the public (https://github.com/...) URL. 391 | # This is OK, since release:perform does not need write access to the repo. 392 | $DRY_RUN sed -i.bak -e 's|^scm.url=scm\\:git\\:git@github.com\\:|scm.url=scm\\:git\\:https\\://github.com/|' release.properties && 393 | $DRY_RUN rm release.properties.bak && 394 | $DRY_RUN git checkout "$tag" && 395 | $DRY_RUN git add -f release.properties && 396 | $DRY_RUN git commit --amend --no-edit && 397 | $DRY_RUN git tag -d "$tag" && 398 | $DRY_RUN git tag "$tag" HEAD && 399 | $DRY_RUN git checkout @{-1} && 400 | 401 | # Push the current branch and the tag. 402 | if test -z "$SKIP_PUSH" 403 | then 404 | debug "Pushing changes" 405 | $DRY_RUN git push "$REMOTE" HEAD $tag 406 | fi 407 | 408 | # Remove files generated by the release process. They can end up 409 | # committed to the mainline branch and hosing up later releases. 410 | debug "Cleaning up" 411 | $DRY_RUN rm -f release.properties pom.xml.releaseBackup 412 | 413 | debug "Release complete!" 414 | -------------------------------------------------------------------------------- /melting-pot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ============================================================================ 4 | # melting-pot.sh 5 | # ============================================================================ 6 | # Tests all components of a project affected by changes in its dependencies. 7 | # 8 | # In particular, this script detects problems caused by diamond dependency 9 | # structures: 10 | # 11 | # https://jlbp.dev/what-is-a-diamond-dependency-conflict 12 | # 13 | # This "melting pot" build rebuilds every dependency of a project, but at 14 | # unified dependency versions matching those of the toplevel project. 15 | # 16 | # For example, net.imagej:imagej:2.5.0 depends on many components including 17 | # org.scijava:scijava-common:2.88.1 and net.imagej:imagej-ops:0.46.1, 18 | # both of which depend on org.scijava:parsington. But: 19 | # 20 | # - org.scijava:scijava-common:2.88.1 depends on org.scijava:parsington:3.0.0 21 | # - net.imagej:imagej-ops:0.46.1 depends on org.scijava:parsington:2.0.0 22 | # 23 | # ImageJ2 can only depend on one of these versions at runtime. The newer one, 24 | # ideally. SciJava projects use the pom-scijava parent POM as a Bill of 25 | # Materials (BOM) to declare these winning versions, which works great... 26 | # EXCEPT for when newer versions break backwards compatibility, as happened 27 | # here: it's a SemVer-versioned project at different major version numbers. 28 | # 29 | # Enter this melting-pot script. It rebuilds each project dependency from 30 | # source and runs the unit tests, but with all dependency versions pinned to 31 | # match those of the toplevel project. 32 | # 33 | # So in the example above, this script: 34 | # 35 | # 1. gathers the dependencies of net.imagej:imagej:2.5.0; 36 | # 2. clones each dependency from SCM at the correct release tag; 37 | # 3. rebuilds each dependency, but with dependency versions overridden to 38 | # those of net.imagej:imagej:2.5.0 rather than those originally used for 39 | # that dependency at that release. 40 | # 41 | # So e.g. in the above scenario, net.imagej:imagej-ops:0.46.1 will be rebuilt 42 | # against org.scijava:parsington:3.0.0, and we will discover whether any of 43 | # parsington's breaking API changes from 2.0.0 to 3.0.0 actually impact the 44 | # compilation or (tested) runtime behavior of imagej-ops. 45 | # 46 | # IMPORTANT IMPLEMENTATION DETAIL! The override works by setting a version 47 | # property for each component of the form "artifactId.version"; it is assumed 48 | # that all components declare their dependencies using version properties of 49 | # this form. E.g.: 50 | # 51 | # 52 | # com.google.guava 53 | # guava 54 | # ${guava.version} 55 | # 56 | # 57 | # Using dependencyManagement is fine too, as long as it then uses this pattern 58 | # to declare the versions as properties, which can be overridden. 59 | # 60 | # Any dependency which does not declare a version property matching this 61 | # assumption will not be properly overridden in the melting pot! 62 | # 63 | # Author: Curtis Rueden 64 | # ============================================================================ 65 | 66 | # -- Constants -- 67 | 68 | meltingPotCache="$HOME/.cache/scijava/melting-pot" 69 | 70 | # -- Functions -- 71 | 72 | stderr() { >&2 printf "$@\n"; } 73 | debug() { test "$debug" && stderr "+ $@"; } 74 | info() { test "$verbose" && stderr "\e[0;37m[INFO] $@\e[0m"; } 75 | warn() { stderr "\e[0;33m[WARNING] $@\e[0m"; } 76 | error() { stderr "\e[0;31m[ERROR] $@\e[0m"; } 77 | die() { error $1; exit $2; } 78 | unknownArg() { error "Unknown option: $@"; usage=1; } 79 | 80 | checkPrereqs() { 81 | while [ $# -gt 0 ] 82 | do 83 | which $1 > /dev/null 2> /dev/null 84 | test $? -ne 0 && die "Missing prerequisite: $1" 255 85 | shift 86 | done 87 | } 88 | 89 | verifyPrereqs() { 90 | checkPrereqs git mvn xmllint 91 | git --version | grep -q 'git version 2' || 92 | die "Please use git v2.x; older versions (<=1.7.9.5 at least) mishandle 'git clone --depth 1'" 254 93 | } 94 | 95 | parseArguments() { 96 | while [ $# -gt 0 ] 97 | do 98 | case "$1" in 99 | -b|--branch) 100 | branch="$2" 101 | shift 102 | ;; 103 | -c|--changes) 104 | test "$changes" && changes="$changes,$2" || changes="$2" 105 | shift 106 | ;; 107 | -i|--includes) 108 | test "$includes" && includes="$includes,$2" || includes="$2" 109 | shift 110 | ;; 111 | -e|--excludes) 112 | test "$excludes" && excludes="$excludes,$2" || excludes="$2" 113 | shift 114 | ;; 115 | -r|--remoteRepos) 116 | test "$remoteRepos" && remoteRepos="$remoteRepos,$2" || remoteRepos="$2" 117 | shift 118 | ;; 119 | -l|--localRepo) 120 | repoBase="$2" 121 | shift 122 | ;; 123 | -o|--outputDir) 124 | outputDir="$2" 125 | shift 126 | ;; 127 | -p|--prune) 128 | prune=1 129 | ;; 130 | -v|--verbose) 131 | verbose=1 132 | ;; 133 | -d|--debug) 134 | debug=1 135 | ;; 136 | -f|--force) 137 | force=1 138 | ;; 139 | -s|--skipBuild) 140 | skipBuild=1 141 | ;; 142 | -h|--help) 143 | usage=1 144 | ;; 145 | -*) 146 | unknownArg "$1" 147 | ;; 148 | *) 149 | test -z "$project" && project="$1" || 150 | unknownArg "$1" 151 | ;; 152 | esac 153 | shift 154 | done 155 | 156 | test -z "$project" -a -z "$usage" && 157 | error "No project specified!" && usage=1 158 | 159 | if [ "$usage" ] 160 | then 161 | echo "Usage: $(basename "$0") [-b ] [-c ] \\ 162 | [-i ] [-e ] [-r ] [-l ] [-o ] [-pvfsh] 163 | 164 | 165 | The project to build, including dependencies, with consistent versions. 166 | Can be either G:A:V form, or a local directory pointing at a project. 167 | -b, --branch 168 | Override the branch/tag of the project to build. By default, 169 | the branch used will be the tag named \"artifactId-version\". 170 | -c, --changes 171 | Comma-separated list of GAVs to inject into the project, replacing 172 | normal versions. E.g.: \"com.mycompany:myartifact:1.2.3-SNAPSHOT\" 173 | -i, --includes 174 | Comma-separated list of GAs (no version; wildcards OK for G or A) to 175 | include in the build. All by default. E.g.: \"mystuff:*,myotherstuff:*\" 176 | -e, --excludes 177 | Comma-separated list of GAs (no version; wildcards OK for G or A) to 178 | exclude from the build. E.g.: \"mystuff:extraneous,mystuff:irrelevant\" 179 | -r, --remoteRepos 180 | Comma-separated list of additional remote Maven repositories to check 181 | for artifacts, in the format id::[layout]::url or just url. 182 | -l, --localRepos 183 | Overrides the directory of the Maven local repository cache. 184 | -o, --outputDir 185 | Overrides the output directory. The default is \"melting-pot\". 186 | -p, --prune 187 | Build only the components which themselves depend on a changed 188 | artifact. This will make the build much faster, at the expense of 189 | not fully testing runtime compatibility across all components. 190 | -v, --verbose 191 | Enable verbose/debugging output. 192 | -f, --force 193 | Wipe out the output directory if it already exists. 194 | -s, --skipBuild 195 | Skips the final build step. Useful for automated testing. 196 | -h, --help 197 | Display this usage information. 198 | 199 | --== Example ==-- 200 | 201 | sh melting-pot.sh net.imagej:imagej-common:0.15.1 \\ 202 | -r https://maven.scijava.org/content/groups/public \\ 203 | -c org.scijava:scijava-common:2.44.2 \\ 204 | -i 'org.scijava:*,net.imagej:*,net.imglib2:*,io.scif:*' \\ 205 | -e net.imglib2:imglib2-roi \\ 206 | -v -f -s 207 | 208 | This command tests net.imagej:imagej-common:0.15.1 along with all of its 209 | dependencies, pulled from its usual SciJava Maven repository location. 210 | 211 | The -c flag is used to override the org.scijava:scijava-common 212 | dependency to use version 2.44.2 instead of its declared version 2.42.0. 213 | 214 | Note that such overrides do not need to be release versions; you can 215 | also test SNAPSHOTs the same way. 216 | 217 | The -i option is used to include all imagej-common dependencies with 218 | groupIds org.scijava, net.imagej, net.imglib2 and io.scif in the pot. 219 | 220 | The -e flag is used to exclude net.imglib2:imglib2-roi from the pot. 221 | " 222 | exit 1 223 | fi 224 | 225 | # If project is a local directory path, get its absolute path. 226 | test -d "$project" && project=$(cd "$project" && pwd) 227 | 228 | # Assign default parameter values. 229 | test "$outputDir" || outputDir="melting-pot" 230 | test "$repoBase" || repoBase="$HOME/.m2/repository" 231 | } 232 | 233 | createDir() { 234 | test -z "$force" -a -e "$1" && 235 | die "Directory already exists: $1" 2 236 | 237 | rm -rf "$1" 238 | mkdir -p "$1" 239 | cd "$1" 240 | } 241 | 242 | groupId() { 243 | echo "${1%%:*}" 244 | } 245 | 246 | artifactId() { 247 | local result="${1#*:}" # strip groupId 248 | echo "${result%%:*}" 249 | } 250 | 251 | version() { 252 | local result="${1#*:}" # strip groupId 253 | case "$result" in 254 | *:*) 255 | result="${result#*:}" # strip artifactId 256 | case "$result" in 257 | *:*:*:*) 258 | # G:A:P:C:V:S 259 | result="${result#*:}" # strip packaging 260 | result="${result#*:}" # strip classifier 261 | ;; 262 | *:*:*) 263 | # G:A:P:V:S 264 | result="${result#*:}" # strip packaging 265 | ;; 266 | *) 267 | # G:A:V or G:A:V:? 268 | ;; 269 | esac 270 | echo "${result%%:*}" 271 | ;; 272 | esac 273 | } 274 | 275 | classifier() { 276 | local result="${1#*:}" # strip groupId 277 | case "$result" in 278 | *:*) 279 | result="${result#*:}" # strip artifactId 280 | case "$result" in 281 | *:*:*:*) 282 | # G:A:P:C:V:S 283 | result="${result#*:}" # strip packaging 284 | ;; 285 | *:*:*) 286 | # G:A:P:V:S 287 | result="" 288 | ;; 289 | *:*) 290 | # G:A:V:C 291 | result="${result#*:}" # strip version 292 | ;; 293 | *) 294 | # G:A:V 295 | result="" 296 | ;; 297 | esac 298 | echo "${result%%:*}" 299 | ;; 300 | esac 301 | } 302 | 303 | # Converts the given GAV into a path in the local repository cache. 304 | repoPath() { 305 | local gPath="$(echo "$(groupId "$1")" | tr :. /)" 306 | local aPath="$(artifactId "$1")" 307 | local vPath="$(version "$1")" 308 | echo "$repoBase/$gPath/$aPath/$vPath" 309 | } 310 | 311 | # Gets the path to the given GAV's POM file in the local repository cache. 312 | pomPath() { 313 | local pomFile="$(artifactId "$1")-$(version "$1").pom" 314 | echo "$(repoPath "$1")/$pomFile" 315 | } 316 | 317 | # Fetches the POM for the given GAV into the local repository cache. 318 | downloadPOM() { 319 | local g="$(groupId "$1")" 320 | local a="$(artifactId "$1")" 321 | local v="$(version "$1")" 322 | debug "mvn dependency:get \\ 323 | -DremoteRepositories=\"$remoteRepos\" \\ 324 | -DgroupId=\"$g\" \\ 325 | -DartifactId=\"$a\" \\ 326 | -Dversion=\"$v\" \\ 327 | -Dpackaging=pom" 328 | mvn dependency:get \ 329 | -DremoteRepositories="$remoteRepos" \ 330 | -DgroupId="$g" \ 331 | -DartifactId="$a" \ 332 | -Dversion="$v" \ 333 | -Dpackaging=pom > /dev/null || 334 | die "Problem fetching $g:$a:$v from $remoteRepos" 4 335 | } 336 | 337 | # Gets the POM path for the given GAV, ensuring it exists locally. 338 | pom() { 339 | local pomPath="$(pomPath "$1")" 340 | test -f "$pomPath" || downloadPOM "$1" 341 | test -f "$pomPath" || die "Cannot access POM: $pomPath" 9 342 | echo "$pomPath" 343 | } 344 | 345 | # For the given XML file on disk ($1), gets the value of the 346 | # specified XPath expression of the form "//$2/$3/$4/...". 347 | xpath() { 348 | local xmlFile="$1" 349 | shift 350 | local expression="$@" 351 | local xpath="/" 352 | while [ $# -gt 0 ] 353 | do 354 | # NB: Ignore namespace issues; see: http://stackoverflow.com/a/8266075 355 | xpath="$xpath/*[local-name()='$1']" 356 | shift 357 | done 358 | local value=$(xmllint --xpath "$xpath" "$xmlFile" 2> /dev/null | 359 | sed -E 's/^[^>]*>(.*)<[^<]*$/\1/') 360 | debug "xpath $xmlFile $expression -> $value" 361 | echo "$value" 362 | } 363 | 364 | # For the given GAV ($1), recursively gets the value of the 365 | # specified XPath expression of the form "//$2/$3/$4/...". 366 | pomValue() { 367 | local pomPath="$(pom "$1")" 368 | test "$pomPath" || die "Cannot discern POM path for $1" 6 369 | shift 370 | local value="$(xpath "$pomPath" $@)" 371 | if [ "$value" ] 372 | then 373 | echo "$value" 374 | else 375 | # Path not found in POM; look in the parent POM. 376 | local pg="$(xpath "$pomPath" project parent groupId)" 377 | if [ "$pg" ] 378 | then 379 | # There is a parent POM declaration in this POM. 380 | local pa="$(xpath "$pomPath" project parent artifactId)" 381 | local pv="$(xpath "$pomPath" project parent version)" 382 | pomValue "$pg:$pa:$pv" $@ 383 | fi 384 | fi 385 | } 386 | 387 | # Gets the SCM URL for the given GAV. 388 | scmURL() { 389 | pomValue "$1" project scm connection | sed 's/^scm:git://' | 390 | sed 's_git:\(//github.com/\)_https:\1_' 391 | } 392 | 393 | # Gets the SCM tag for the given GAV. 394 | scmTag() { 395 | local tag=$(pomValue "$1" project scm tag) 396 | if [ -z "$tag" -o "$tag" = "HEAD" ] 397 | then 398 | # The value was not set properly, 399 | # so we try to guess the tag naming scheme. :-/ 400 | warn "$1: improper scm tag value; scanning remote tags..." 401 | local a=$(artifactId "$1") 402 | local v=$(version "$1") 403 | local scmURL="$(scmURL "$1")" 404 | # TODO: Avoid network use. We can scan the locally cached repo. 405 | # But this gets complicated when the locally cached repo is 406 | # out of date, and the needed tag is not there yet... 407 | debug "git ls-remote --tags \"$scmURL\" | sed 's/.*refs\/tags\///'" 408 | local allTags="$(git ls-remote --tags "$scmURL" | sed 's/.*refs\/tags\///' || 409 | error "$1: Invalid scm url: $scmURL")" 410 | for tag in "$a-$v" "$v" "v$v" 411 | do 412 | echo "$allTags" | grep -q "^$tag$" && { 413 | info "$1: inferred tag: $tag" 414 | echo "$tag" 415 | return 416 | } 417 | done 418 | error "$1: inscrutable tag scheme -- using default branch" 419 | else 420 | echo "$tag" 421 | fi 422 | } 423 | 424 | # Ensures the source code for the given GAV exists in the melting-pot 425 | # structure, and is up-to-date with the remote. Returns the directory. 426 | resolveSource() { 427 | local g=$(groupId "$1") 428 | local a=$(artifactId "$1") 429 | local cachedRepoDir="$meltingPotCache/$g/$a" 430 | if [ ! -d "$cachedRepoDir" ] 431 | then 432 | # Source does not exist locally. Clone it into the melting pot cache. 433 | local scmURL="$(scmURL "$1")" 434 | test "$scmURL" || die "$1: cannot glean SCM URL" 10 435 | info "$1: cached repository not found; cloning from remote: $scmURL" 436 | debug "git clone --bare \"$scmURL\" \"$cachedRepoDir\"" 437 | git clone --bare "$scmURL" "$cachedRepoDir" 2> /dev/null || 438 | die "$1: could not clone project source from $scmURL" 3 439 | fi 440 | 441 | # Check whether the needed branch/tag exists. 442 | local scmBranch 443 | test "$2" && scmBranch="$2" || scmBranch="$(scmTag "$1")" 444 | 445 | if [ "$scmBranch" ] 446 | then 447 | # Successfully gleaned SCM branch/tag. 448 | debug "git ls-remote \"file://$cachedRepoDir\" | grep -q \"\brefs/tags/$scmBranch$\"" 449 | git ls-remote "file://$cachedRepoDir" | grep -q "\brefs/tags/$scmBranch$" || { 450 | # Couldn't find the scmBranch as a tag in the cached repo. Either the 451 | # tag is new, or it's not a tag ref at all (e.g. it's a branch). 452 | # So let's update from the original remote repository. 453 | info "$1: local tag not found for ref '$scmBranch'" 454 | info "$1: updating cached repository: $cachedRepoDir" 455 | cd "$cachedRepoDir" 456 | debug "git fetch --tags" 457 | if [ "$debug" ] 458 | then 459 | git fetch --tags 460 | else 461 | git fetch --tags > /dev/null 462 | fi 463 | cd - > /dev/null 464 | } 465 | else 466 | # No SCM branch/tag; fall back to the default branch. 467 | info "$1: updating cached repository: $cachedRepoDir" 468 | cd "$cachedRepoDir" 469 | debug "git fetch" 470 | if [ "$debug" ] 471 | then 472 | git fetch 473 | else 474 | git fetch > /dev/null 475 | fi 476 | head=$(cat HEAD) 477 | scmBranch=${head##*/} 478 | info "$1: detected default branch as $scmBranch" 479 | cd - > /dev/null 480 | fi 481 | 482 | # Shallow clone the source at the given version into melting-pot structure. 483 | local destDir="$g/$a" 484 | debug "git clone \"file://$cachedRepoDir\" --branch \"$scmBranch\" --depth 1 \"$destDir\"" 485 | git clone "file://$cachedRepoDir" --branch "$scmBranch" --depth 1 "$destDir" 2> /dev/null || 486 | die "$1: could not clone branch '$scmBranch' from local cache" 15 487 | 488 | # Save the GAV string to a file, for convenience. 489 | echo "$1" > "$destDir/gav" 490 | 491 | # Now verify that the cloned pom.xml contains the expected version! 492 | local expectedVersion=$(version "$1") 493 | local actualVersion=$(xpath "$destDir/pom.xml" project version) 494 | test "$expectedVersion" = "$actualVersion" || 495 | warn "$1: POM contains wrong version: $actualVersion" 496 | 497 | echo "$destDir" 498 | } 499 | 500 | # Gets the list of dependencies for the project in the CWD. 501 | deps() { 502 | cd "$1" || die "No such directory: $1" 16 503 | debug "mvn -B -DincludeScope=runtime dependency:list" 504 | local depList="$(mvn -B -DincludeScope=runtime dependency:list)" || 505 | die "Problem fetching dependencies!" 5 506 | echo "$depList" | grep '^\[INFO\] \w' | 507 | sed 's/\[INFO\] //' | sed 's/ .*//' | sort 508 | cd - > /dev/null 509 | } 510 | 511 | # Checks whether the given GA(V) matches the specified filter pattern. 512 | gaMatch() { 513 | local ga="$1" 514 | local filter="$2" 515 | local g="$(groupId "$ga")" 516 | local a="$(artifactId "$ga")" 517 | local fg="$(groupId "$filter")" 518 | local fa="$(artifactId "$filter")" 519 | test "$fg" = "$g" -o "$fg" = "*" || return 520 | test "$fa" = "$a" -o "$fa" = "*" || return 521 | echo 1 522 | } 523 | 524 | # Determines whether the given GA(V) version is being overridden. 525 | isChanged() { 526 | local IFS="," 527 | 528 | local change 529 | for change in $changes 530 | do 531 | test "$(gaMatch "$1" "$change")" && echo 1 && return 532 | done 533 | } 534 | 535 | # Determines whether the given GA(V) meets the inclusion criteria. 536 | isIncluded() { 537 | # do not include the changed artifacts we are testing against 538 | test "$(isChanged "$1")" && return 539 | 540 | local IFS="," 541 | 542 | # ensure GA is not excluded 543 | local exclude 544 | for exclude in $excludes 545 | do 546 | test "$(gaMatch "$1" "$exclude")" && return 547 | done 548 | 549 | # ensure GA is included 550 | test -z "$includes" && echo 1 && return 551 | local include 552 | for include in $includes 553 | do 554 | test "$(gaMatch "$1" "$include")" && echo 1 && return 555 | done 556 | } 557 | 558 | # Deletes components which do not depend on a changed GAV. 559 | pruneReactor() { 560 | local dir 561 | for dir in */* 562 | do 563 | info "Checking relevance of component $dir" 564 | local deps="$(deps "$dir")" 565 | test "$deps" || die "Cannot glean dependencies for '$dir'" 8 566 | 567 | # Determine whether the component depends on a changed GAV. 568 | local keep 569 | unset keep 570 | local dep 571 | for dep in $deps 572 | do 573 | test "$(isChanged "$dep")" && keep=1 && break 574 | done 575 | 576 | # If the component is irrelevant, prune it. 577 | if [ -z "$keep" ] 578 | then 579 | info "Pruning irrelevant component: $dir" 580 | rm -rf "$dir" 581 | fi 582 | done 583 | } 584 | 585 | # Tests if the given directory contains the appropriate source code. 586 | isProject() { 587 | local a="$(xpath "$1/pom.xml" project artifactId)" 588 | test "$1" = "LOCAL/PROJECT" -o "$a" = "$(basename "$1")" && echo 1 589 | } 590 | 591 | # Generates melt.sh, covering all projects in the current directory. 592 | generateMeltScript() { 593 | echo '#!/bin/sh' > melt.sh 594 | echo 'trap "exit" INT' >> melt.sh 595 | echo 'echo "Melting the pot..."' >> melt.sh 596 | echo 'dir=$(cd "$(dirname "$0")" && pwd)' >> melt.sh 597 | echo 'failCount=0' >> melt.sh 598 | echo 'for f in \' >> melt.sh 599 | local dir 600 | for dir in */* 601 | do 602 | if [ "$(isProject "$dir")" ] 603 | then 604 | echo " $dir \\" >> melt.sh 605 | else 606 | # Check for a child component of a multi-module project. 607 | local childDir="$dir/$(basename "$dir")" 608 | test "$(isProject "$childDir")" && 609 | echo " $childDir \\" >> melt.sh 610 | fi 611 | done 612 | echo >> melt.sh 613 | echo 'do' >> melt.sh 614 | echo ' if [ "$("$dir/prior-success.sh" "$f")" ]' >> melt.sh 615 | echo ' then' >> melt.sh 616 | echo ' printf "\e[0;36m[SKIPPED] $f (prior success)\e[0m\n"' >> melt.sh 617 | echo ' continue' >> melt.sh 618 | echo ' fi' >> melt.sh 619 | echo ' cd "$f"' >> melt.sh 620 | echo ' "$dir/build.sh" >build.log 2>&1 && {' >> melt.sh 621 | echo ' printf "\e[0;32m[SUCCESS] $f\e[0m\n"' >> melt.sh 622 | echo ' "$dir/record-success.sh" "$f"' >> melt.sh 623 | echo ' } || {' >> melt.sh 624 | echo ' printf "\e[0;31m[FAILURE] $f\e[0m\n"' >> melt.sh 625 | echo ' failCount=$((failCount+1))' >> melt.sh 626 | echo ' }' >> melt.sh 627 | echo ' cd - >/dev/null' >> melt.sh 628 | echo 'done' >> melt.sh 629 | echo 'test "$failCount" -gt 255 && failCount=255' >> melt.sh 630 | echo 'exit "$failCount"' >> melt.sh 631 | chmod +x melt.sh 632 | } 633 | 634 | # Generates helper scripts, including prior-success.sh and record-success.sh. 635 | generateHelperScripts() { 636 | cat <<\PRIOR > prior-success.sh 637 | #!/bin/sh 638 | test "$1" || { 639 | printf "\e[0;31m[ERROR] Please specify project to check.\e[0m\n" 640 | exit 1 641 | } 642 | 643 | stderr() { >&2 printf "$@\n"; } 644 | debug() { test "$DEBUG" && stderr "\e[0;37m[DEBUG] $@\e[0m"; } 645 | info() { stderr "\e[0;37m[INFO] $@\e[0m"; } 646 | warn() { stderr "\e[0;33m[WARNING] $@\e[0m"; } 647 | 648 | dir=$(cd "$(dirname "$0")" && pwd) 649 | 650 | # Check build.log for BUILD SUCCESS. 651 | buildLog="$dir/$1/build.log" 652 | test -f "$buildLog" && tail -n6 "$buildLog" | grep -qF 'BUILD SUCCESS' && { 653 | echo "build.log" 654 | exit 0 655 | } 656 | 657 | # Check success.log for matching dependency configuration. 658 | successLog="$HOME/.cache/scijava/melting-pot/$1.success.log" 659 | test -f "$successLog" || exit 0 660 | row=1 661 | mismatch1= 662 | success= 663 | for deps in $(cat "$successLog") 664 | do 665 | debug "$1: Checking dep config: $deps" 666 | mismatch= 667 | for dep in $(echo "$deps" | tr ',' '\n') 668 | do 669 | # g:a:p:v:s -> v 670 | s=${dep##*:} 671 | case "$s" in 672 | test) continue ;; # skip test dependencies 673 | none) continue ;; # empty dependency config 674 | esac 675 | gapv=${dep%:*} 676 | g=${gapv%%:*} 677 | apv=${gapv#*:} 678 | a=${apv%%:*} 679 | v=${apv##*:} 680 | bomV=$(grep -o " <$g\.$a\.version>[^>]*" "$dir/version-pins.xml" | sed 's;[^>]*>\([^>]*\)<.*;\1;') 681 | if [ "$bomV" != "${bomV#*-SNAPSHOT*}" ] 682 | then 683 | warn "$1: Snapshot dependency pin detected: $g:$a:$bomV -- forcing a rebuild" 684 | exit 0 685 | elif [ "$bomV" != "$v" ] 686 | then 687 | # G:A property is not set to this V. 688 | # Now check if the property is even declared. 689 | if [ "$bomV" ] 690 | then 691 | # G:A version is mismatched. 692 | mismatch="$mismatch\n* $g:$a:$v -> $bomV" 693 | else 694 | # G:A version is not pinned. 695 | test "$row" -ne 1 || 696 | warn "$1: Unpinned dependency: $dep" 697 | fi 698 | fi 699 | done 700 | if [ "$mismatch" ] 701 | then 702 | test "$row" -eq 1 && mismatch1=$mismatch || 703 | debug "$1: Dependency changes since success #$row:$mismatch" 704 | else 705 | success=$deps 706 | break 707 | fi 708 | row=$((row+1)) 709 | done 710 | test "$success" && echo "$success" || { 711 | test "$mismatch1" && 712 | info "$1: Dependency changes since last success:$mismatch1" || 713 | info "$1: No prior successes" 714 | } 715 | PRIOR 716 | chmod +x prior-success.sh 717 | 718 | cat <<\RECORD > record-success.sh 719 | #!/bin/sh 720 | test "$1" || { 721 | printf "\e[0;31m[ERROR] Please specify project to update.\e[0m\n" 722 | exit 1 723 | } 724 | 725 | containsLine() { 726 | pattern=$1 727 | file=$2 728 | test -f "$file" || return 729 | # HACK: The obvious way to do this is: 730 | # 731 | # grep -qxF "$pattern" "$file" 732 | # 733 | # Unfortunately, BSD grep dies with "out of memory" when the pattern is 5111 734 | # characters or longer. So let's do something needlessly complex instead! 735 | cat "$file" | while read line 736 | do 737 | test "$pattern" = "$line" && echo 1 && break 738 | done 739 | } 740 | 741 | dir=$(cd "$(dirname "$0")" && pwd) 742 | buildLog="$dir/$1/build.log" 743 | test -f "$buildLog" || exit 1 744 | successLog="$HOME/.cache/scijava/melting-pot/$1.success.log" 745 | mkdir -p "$(dirname "$successLog")" 746 | 747 | # Record dependency configuration of successful build. 748 | deps=$(grep '^\[[^ ]*INFO[^ ]*\] \w' "$buildLog" | 749 | sed -e 's/^[^ ]* *//' -e 's/ -- .*//' -e 's/ (\([^)]*\))/-\1/' | 750 | sort | tr '\n' ',') 751 | if [ "$deps" = "${deps%*-SNAPSHOT*}" -a -z "$(containsLine "$deps" "$successLog")" ] 752 | then 753 | # NB: *Prepend*, rather than append, the new successful configuration. 754 | # We do this because it is more likely this new configuration will be 755 | # encountered again in the future, as dependency versions are highly 756 | # likely to repeatedly increment, rather than moving backwards. 757 | echo "$deps" > "$successLog".new 758 | test -f "$successLog" && cat "$successLog" >> "$successLog".new 759 | mv -f "$successLog".new "$successLog" 760 | fi 761 | RECORD 762 | chmod +x record-success.sh 763 | } 764 | 765 | # Creates and tests an appropriate multi-module reactor for the given project. 766 | # All relevant dependencies which match the inclusion criteria are linked into 767 | # the multi-module build, with each changed GAV overridding the originally 768 | # specified version for the corresponding GA. 769 | meltDown() { 770 | # Fetch the project source code. 771 | if [ -d "$1" ] 772 | then 773 | # Use local directory for the specified project. 774 | test -d "$1" || die "No such directory: $1" 11 775 | test -f "$1/pom.xml" || die "Not a Maven project: $1" 12 776 | case "$(uname)" in 777 | MINGW*) 778 | warn "Skipping inclusion of local project due to lack of symlink support." 779 | local projectDir="$1" 780 | ;; 781 | *) 782 | info "Local Maven project: $1" 783 | mkdir -p "LOCAL" 784 | local projectDir="LOCAL/PROJECT" 785 | ln -s "$1" "$projectDir" 786 | ;; 787 | esac 788 | else 789 | # Treat specified project as a GAV. 790 | info "Fetching project source" 791 | local projectDir=$(resolveSource "$1" "$branch") 792 | test $? -eq 0 || exit $? 793 | fi 794 | 795 | # Get the project dependencies. 796 | info "Determining project dependencies" 797 | local deps="$(deps "$projectDir")" 798 | test "$deps" || die "Cannot glean project dependencies" 7 799 | 800 | # Generate helper scripts. We need prior-success.sh 801 | # to decide whether to include each component. 802 | generateHelperScripts 803 | 804 | # Process the dependencies. 805 | info "Processing project dependencies" 806 | local versionProps="" 807 | local dep 808 | for dep in $deps 809 | do 810 | local g="$(groupId "$dep")" 811 | local a="$(artifactId "$dep")" 812 | local aa 813 | echo "$a" | grep -q '^[0-9]' && aa="_$a" || aa="$a" 814 | local v="$(version "$dep")" 815 | local c="$(classifier "$dep")" 816 | test -z "$c" || continue # skip secondary artifacts 817 | local gav="$g:$a:$v" 818 | test -z "$(isChanged "$gav")" && 819 | versionProps="$versionProps 820 | <$g.$a.version>$v <$aa.version>$v" 821 | done 822 | 823 | # Override versions of changed GAVs. 824 | info "Processing changed components" 825 | local TLS=, 826 | local gav 827 | for gav in $changes 828 | do 829 | local a="$(artifactId "$gav")" 830 | local v="$(version "$gav")" 831 | versionProps="$versionProps 832 | <$a.version>$v" 833 | done 834 | unset TLS 835 | 836 | # Generate version-pins.xml. 837 | info "Generating version-pins.xml configuration" 838 | echo ' version-pins.xml 839 | echo ' xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 https://maven.apache.org/xsd/settings-1.1.0.xsd">' >> version-pins.xml 840 | echo ' ' >> version-pins.xml 841 | echo ' ' >> version-pins.xml 842 | echo ' version-pins' >> version-pins.xml 843 | echo ' ' >> version-pins.xml 844 | echo ' true' >> version-pins.xml 845 | echo ' ' >> version-pins.xml 846 | echo ' ' >> version-pins.xml 847 | echo "$versionProps" >> version-pins.xml 848 | echo ' ' >> version-pins.xml 849 | echo ' ' >> version-pins.xml 850 | echo ' ' >> version-pins.xml 851 | echo '' >> version-pins.xml 852 | 853 | # Generate build script. 854 | info "Generating build.sh script" 855 | echo '#!/bin/sh' > build.sh 856 | echo >> build.sh 857 | echo 'dir=$(cd "$(dirname "$0")" && pwd)' >> build.sh 858 | echo >> build.sh 859 | echo 'mvnPin() {' >> build.sh 860 | # NB: We do *not* include -B here, because we want build.sh to preserve 861 | # colored output if the version of Maven is new enough. We will take care 862 | # elsewhere when parsing it to be flexible about whether colors are present. 863 | echo ' mvn -s "$dir/version-pins.xml" -Denforcer.skip $@' >> build.sh 864 | echo '}' >> build.sh 865 | echo >> build.sh 866 | echo 'unpackArtifact() {' >> build.sh 867 | echo ' # Download and unpack the given artifact' >> build.sh 868 | echo ' # (G:A:V) to the specified location.' >> build.sh 869 | echo ' gav=$1' >> build.sh 870 | echo ' out=$2' >> build.sh 871 | echo >> build.sh 872 | echo ' repoPrefix=$HOME/.m2/repository # TODO: generalize this' >> build.sh 873 | echo ' g=${gav%%:*}; r=${gav#*:}; a=${r%%:*}; v=${r##*:}' >> build.sh 874 | echo ' gavPath="$(echo "$g" | tr "." "/")/$a/$v/$a-$v"' >> build.sh 875 | echo ' jarPath="$repoPrefix/$gavPath.jar"' >> build.sh 876 | echo >> build.sh 877 | echo ' # HACK: The best goal to use would be dependency:unpack,' >> build.sh 878 | echo ' # or failing that, dependency:copy followed by jar xf.' >> build.sh 879 | echo ' # But those goals do not support remoteRepositories;' >> build.sh 880 | echo ' # see https://issues.apache.org/jira/browse/MDEP-390.' >> build.sh 881 | echo ' # So we use dependency:get and then extract it by hand.' >> build.sh 882 | echo ' mvnPin dependency:get \' >> build.sh 883 | echo " -DremoteRepositories=\"$remoteRepos\" \\" >> build.sh 884 | echo ' -Dartifact="$gav" &&' >> build.sh 885 | echo >> build.sh 886 | echo ' test -f "$jarPath" &&' >> build.sh 887 | echo ' mkdir -p "$out" &&' >> build.sh 888 | echo ' cd "$out" &&' >> build.sh 889 | echo ' jar xf "$jarPath" &&' >> build.sh 890 | echo ' cd - >/dev/null' >> build.sh 891 | echo '}' >> build.sh 892 | echo >> build.sh 893 | echo 'mvnPin dependency:list dependency:tree &&' >> build.sh 894 | echo >> build.sh 895 | echo 'if [ -f gav ]' >> build.sh 896 | echo 'then' >> build.sh 897 | echo ' echo' >> build.sh 898 | echo ' echo "================================================"' >> build.sh 899 | echo ' echo "========= Testing with deployed binary ========="' >> build.sh 900 | echo ' echo "================================================"' >> build.sh 901 | echo ' unpackArtifact "$(cat gav)" target/classes &&' >> build.sh 902 | echo ' mvnPin \' >> build.sh 903 | echo ' -Dmaven.main.skip=true \' >> build.sh 904 | echo ' -Dmaven.resources.skip=true \' >> build.sh 905 | echo ' test $@' >> build.sh 906 | echo 'fi &&' >> build.sh 907 | echo >> build.sh 908 | echo 'echo &&' >> build.sh 909 | echo 'echo "================================================" &&' >> build.sh 910 | echo 'echo "============ Rebuilding from source ============" &&' >> build.sh 911 | echo 'echo "================================================" &&' >> build.sh 912 | echo 'mvnPin clean test $@' >> build.sh 913 | chmod +x build.sh 914 | 915 | # Clone source code. 916 | info "Cloning source code" 917 | for dep in $deps 918 | do 919 | local g="$(groupId "$dep")" 920 | local a="$(artifactId "$dep")" 921 | local v="$(version "$dep")" 922 | local c="$(classifier "$dep")" 923 | test -z "$c" || continue # skip secondary artifacts 924 | local gav="$g:$a:$v" 925 | if [ "$(isIncluded "$gav")" ] 926 | then 927 | if [ "$v" != "${v%-SNAPSHOT}" ] 928 | then 929 | info "$g:$a: forcing inclusion due to SNAPSHOT version" 930 | elif [ "$(./prior-success.sh "$g/$a")" ] 931 | then 932 | info "$g:$a: skipping version $v due to prior successful build" 933 | continue 934 | fi 935 | info "$g:$a: resolving source for version $v" 936 | resolveSource "$gav" >/dev/null 937 | fi 938 | done 939 | 940 | # Prune the build, if applicable. 941 | test "$prune" && pruneReactor 942 | 943 | # Generate melt script. 944 | info "Generating melt.sh script" 945 | generateMeltScript 946 | 947 | # Build everything. 948 | if [ "$skipBuild" ] 949 | then 950 | info "Skipping the build; run melt.sh to do it." 951 | else 952 | info "Building the project!" 953 | # NB: All code is fresh; no need to clean. 954 | sh melt.sh || die "Melt failed" 13 955 | fi 956 | 957 | info "Melt complete: $1" 958 | } 959 | 960 | # -- Main -- 961 | 962 | verifyPrereqs 963 | parseArguments $@ 964 | createDir "$outputDir" 965 | meltDown "$project" 966 | --------------------------------------------------------------------------------