See More

#!/bin/bash # # ci-build.sh - A script to build and/or release SciJava-based projects # automatically using a continuous integration service. # # Optional environment variables: # BUILD_REPOSITORY - the repository URL running the current build dir="$(dirname "$0")" platform=$(uname -s) success=0 checkSuccess() { # Log non-zero exit code. test $1 -eq 0 || echo "==> FAILED: EXIT CODE $1" 1>&2 if [ $1 -ne 0 -a -f "$2" ] then # The operation failed and a log file was provided. # Do some heuristics, because we like being helpful! javadocErrors=$(grep error: "$2") generalErrors=$(grep -i '\b\(errors\?\|fail\|failures\?\)\b' "$2") if [ "$javadocErrors" ] then echo echo '/----------------------------------------------------------\' echo '| ci-build.sh analysis: I noticed probable javadoc errors: |' echo '\----------------------------------------------------------/' echo "$javadocErrors" elif [ "$generalErrors" ] then echo echo '/-------------------------------------------------------\' echo '| ci-build.sh analysis: I noticed the following errors: |' echo '\-------------------------------------------------------/' echo "$generalErrors" else echo echo '/----------------------------------------------------------------------\' echo '| ci-build.sh analysis: I see no problems in the operation log. Sorry! |' echo '\----------------------------------------------------------------------/' echo fi fi # Record the first non-zero exit code. test $success -eq 0 && success=$1 } # Credit: https://stackoverflow.com/a/12873723/1207769 escapeXML() { echo "$1" | sed 's/&/\&/g; s/\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/g' } mavenEvaluate() { mvn -B -U -q -Denforcer.skip=true -Dexec.executable=echo -Dexec.args="$1" --non-recursive validate exec:exec 2>&1 } # Output debugging info, for troubleshooting builds. dump() { if env | grep -q ^$1= then # Environment variable is set. line=$(env | grep ^$1=) if [ "$line" ] then # And it's non-empty. if [ "$2" ] then # Secret value: emit only that it is present. echo "$1=" else # Not a secret: emit in plaintext. echo "$line" fi else # But it is empty! echo "$1=" fi else # Environment variable is not set. echo "$1=" fi } analyzeGitHubWorkflow() { test -d .github/workflows || return echo echo '/--------------------------------------------------\' echo '| ci-build.sh analysis: env vars + GitHub workflow |' echo '\--------------------------------------------------/' dump dir dump platform dump BUILD_REPOSITORY dump NO_DEPLOY echo '----------------------------------------------------' dump GPG_KEY_NAME secret dump GPG_PASSPHRASE secret dump MAVEN_USER secret dump MAVEN_PASS secret dump CENTRAL_USER secret dump CENTRAL_PASS secret dump SIGNING_ASC secret echo '----------------------------------------------------' for var in \ GPG_KEY_NAME \ GPG_PASSPHRASE \ MAVEN_USER \ MAVEN_PASS \ CENTRAL_USER \ CENTRAL_PASS \ SIGNING_ASC do grep -q "secrets.$var" .github/workflows/*.yml || echo "Add \`$var: \${{ secrets.$var }}\` to GitHub workflow env section!" done } # Build Maven projects. if [ -f pom.xml ]; then echo ::group::"= Maven build =" # --== MAVEN SETUP ==-- echo echo '== Configuring Maven ==' # NB: Suppress "Downloading/Downloaded" messages. # See: https://stackoverflow.com/a/35653426/1207769 export MAVEN_OPTS="$MAVEN_OPTS -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" # Populate the settings.xml configuration. mkdir -p "$HOME/.m2" settingsFile="$HOME/.m2/settings.xml" customSettings=.ci/settings.xml if [ "$OSSRH_USER" -o "$OSSRH_PASS" ]; then echo '[WARNING] Obsolete OSSRH vars detected. Secrets may need updating to deploy to Maven Central.' fi if [ -f "$customSettings" ]; then cp "$customSettings" "$settingsFile" elif [ -z "$BUILD_REPOSITORY" ]; then echo 'Skipping settings.xml generation (no BUILD_REPOSITORY; assuming we are running locally)' else # settings.xml header cat >"$settingsFile" < EOL # settings.xml scijava servers if [ "$MAVEN_USER" -a "$MAVEN_PASS" ]; then cat >>"$settingsFile" < scijava.releases $MAVEN_USER $(escapeXML "$MAVEN_PASS") scijava.snapshots $MAVEN_USER $(escapeXML "$MAVEN_PASS") EOL else echo '[WARNING] Skipping settings.xml scijava servers (no MAVEN deployment credentials).' fi # settings.xml central server if [ "$CENTRAL_USER" -a "$CENTRAL_PASS" ]; then cat >>"$settingsFile" < central $CENTRAL_USER $(escapeXML "$CENTRAL_PASS") EOL else echo '[WARNING] Skipping settings.xml central server (no CENTRAL deployment credentials).' fi cat >>"$settingsFile" < EOL # settings.xml GPG profile if [ "$GPG_KEY_NAME" -a "$GPG_PASSPHRASE" ]; then cat >>"$settingsFile" < gpg $HOME/.gnupg $GPG_KEY_NAME $(escapeXML "$GPG_PASSPHRASE") EOL else echo '[WARNING] Skipping settings.xml gpg profile (no GPG credentials).' fi # settings.xml footer cat >>"$settingsFile" < EOL fi # --== DEPLOYMENT CHECKS ==-- # Determine whether deploying is both possible and warranted. echo 'Performing deployment checks' deployOK= scmURL=$(mavenEvaluate '${project.scm.url}') result=$? checkSuccess $result if [ $result -ne 0 ]; then echo 'No deploy -- could not extract ciManagement URL' echo 'Output of failed attempt follows:' echo "$scmURL" else scmURL=${scmURL%.git} scmURL=${scmURL%/} if [ "$NO_DEPLOY" ]; then echo 'No deploy -- the NO_DEPLOY flag is set' elif [ "$BUILD_REPOSITORY" -a "$BUILD_REPOSITORY" != "$scmURL" ]; then echo "No deploy -- repository fork: $BUILD_REPOSITORY != $scmURL" elif [ "$BUILD_BASE_REF" -o "$BUILD_HEAD_REF" ]; then echo "No deploy -- proposed change: $BUILD_HEAD_REF -> $BUILD_BASE_REF" else # Are we building a snapshot version, or a release version? version=$(mavenEvaluate '${project.version}') result=$? checkSuccess $result if [ $result -ne 0 ]; then echo 'No deploy -- could not extract version string' echo 'Output of failed attempt follows:' echo "$version" else case "$version" in *-SNAPSHOT) # Snapshot version -- ensure release.properties not present. if [ -f release.properties ]; then echo '[ERROR] Spurious release.properties file is present' echo 'Remove the file from version control and try again.' exit 1 fi # Check for SciJava Maven repository credentials. if [ "$MAVEN_USER" -a "$MAVEN_PASS" ]; then deployOK=1 else echo 'No deploy -- MAVEN environment variables not available' analyzeGitHubWorkflow fi ;; *) # Release version -- ensure release.properties is present. if [ ! -f release.properties ]; then echo '[ERROR] Release version, but release.properties not found' echo 'You must use release-version.sh to release -- see https://imagej.net/develop/releasing' exit 1 fi # To which repository are we releasing? releaseProfiles=$(mavenEvaluate '${releaseProfiles}') result=$? checkSuccess $result if [ $result -ne 0 ]; then echo 'No deploy -- could not extract releaseProfiles string' echo 'Output of failed attempt follows:' echo "$releaseProfiles" fi case "$releaseProfiles" in *deploy-to-scijava*) # Check for SciJava Maven repository credentials. if [ "$MAVEN_USER" -a "$MAVEN_PASS" ]; then deployOK=1 else echo '[ERROR] Cannot deploy: MAVEN environment variables not available' analyzeGitHubWorkflow exit 1 fi ;; *sonatype-oss-release*) # Check for Central Portal deployment credentials. # Deploy to Central requires GPG-signed artifacts. if [ "$CENTRAL_USER" -a "$CENTRAL_PASS" -a "$SIGNING_ASC" -a "$GPG_KEY_NAME" -a "$GPG_PASSPHRASE" ]; then deployOK=1 else echo '[ERROR] Cannot deploy: CENTRAL environment variables not available' analyzeGitHubWorkflow exit 1 fi ;; *) echo 'Unknown deploy target -- attempting to deploy anyway' deployOK=1 ;; esac ;; esac fi fi fi if [ "$deployOK" ]; then echo 'All checks passed for artifact deployment' fi # --== Maven build arguments ==-- BUILD_ARGS="$BUILD_ARGS -B -Djdk.tls.client.protocols=TLSv1,TLSv1.1,TLSv1.2" # --== GPG SETUP ==-- if [ "$GPG_KEY_NAME" -a "$GPG_PASSPHRASE" ]; then # Install GPG on macOS if [ "$platform" = Darwin ]; then HOMEBREW_NO_AUTO_UPDATE=1 brew install gnupg2 fi # Avoid "signing failed: Inappropriate ioctl for device" error. export GPG_TTY=$(tty) # Import the GPG signing key. keyFile=.ci/signingkey.asc if [ "$deployOK" ]; then echo '== Importing GPG keypair ==' mkdir -p .ci echo "$SIGNING_ASC" > "$keyFile" ls -la "$keyFile" gpg --version gpg --batch --fast-import "$keyFile" checkSuccess $? fi # HACK: Use maven-gpg-plugin 3.0.1+. Avoids "signing failed: No such file or directory" error. maven_gpg_plugin_version=$(mavenEvaluate '${maven-gpg-plugin.version}') case "$maven_gpg_plugin_version" in 0.*|1.*|2.*|3.0.0) echo "--> Forcing maven-gpg-plugin version from $maven_gpg_plugin_version to 3.0.1" BUILD_ARGS="$BUILD_ARGS -Dmaven-gpg-plugin.version=3.0.1 -Darguments=-Dmaven-gpg-plugin.version=3.0.1" ;; *) echo "--> maven-gpg-plugin version OK: $maven_gpg_plugin_version" ;; esac # HACK: Install pinentry helper program if missing. Avoids "signing failed: No pinentry" error. if ! which pinentry >/dev/null 2>&1; then echo '--> Installing missing pinentry helper for GPG' sudo apt-get install -y pinentry-tty # HACK: Restart the gpg agent, to notice the newly installed pinentry. if { pgrep gpg-agent >/dev/null && which gpgconf >/dev/null 2>&1; } then echo '--> Restarting gpg-agent' gpgconf --reload gpg-agent checkSuccess $? fi fi else echo '[WARNING] Skipping gpg setup (no GPG credentials).' fi # --== BUILD EXECUTION ==-- # Run the build. if [ "$deployOK" -a -f release.properties ]; then echo echo '== Cutting and deploying release version ==' BUILD_ARGS="$BUILD_ARGS release:perform" elif [ "$deployOK" ]; then echo echo '== Building and deploying main branch SNAPSHOT ==' BUILD_ARGS="-Pdeploy-to-scijava $BUILD_ARGS deploy" else echo echo '== Building the artifact locally only ==' BUILD_ARGS="$BUILD_ARGS install javadoc:javadoc" fi # Check the build result. { (set -x; mvn $BUILD_ARGS); echo $? > exit-code; } | tee mvn-log checkSuccess "$(cat exit-code)" mvn-log # --== POST-BUILD ACTIONS ==-- # Dump logs for any failing unit tests. if [ -d target/surefire-reports ] then find target/surefire-reports -name '*.txt' | while read report do if grep -qF 'FAILURE!' "$report" then echo echo "[$report]" cat "$report" fi done fi echo ::endgroup:: fi # Execute Jupyter notebooks. if which jupyter >/dev/null 2>&1; then echo ::group::"= Jupyter notebooks =" # NB: This part is fiddly. We want to loop over files even with spaces, # so we use the "find ... | while read ..." idiom. # However, that runs the piped expression in a subshell, which means # that any updates to the success variable will not persist outside # the loop. So we store non-zero success values into a temporary file, # then capture the value back into the parent shell's success variable. find . -name '*.ipynb' | while read nbf do echo echo "== $nbf ==" jupyter nbconvert --to python --stdout --execute "$nbf" checkSuccess $? test "$success" -eq 0 || echo "$success" > success.tmp done test -f success.tmp && success=$(cat success.tmp) && rm success.tmp echo ::endgroup:: fi exit $success