diff --git a/.agents b/.agents index 31f89b3..cfd86bd 160000 --- a/.agents +++ b/.agents @@ -1 +1 @@ -Subproject commit 31f89b30ce3f75f4b381156bf6ee1ce9752de45a +Subproject commit cfd86bdd6113b67c3cd4b281142b29b896a07a0c diff --git a/.github/workflows/agents.yml b/.github/workflows/agents.yml index 5563698..1178ccb 100644 --- a/.github/workflows/agents.yml +++ b/.github/workflows/agents.yml @@ -67,7 +67,7 @@ jobs: concurrency: group: agents-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.number }} queue: max - uses: cinchapi/cinchapi-agent-skills/.github/workflows/router.yml@31f89b30ce3f75f4b381156bf6ee1ce9752de45a + uses: cinchapi/cinchapi-agent-skills/.github/workflows/router.yml@cfd86bdd6113b67c3cd4b281142b29b896a07a0c permissions: contents: read id-token: write diff --git a/build.gradle b/build.gradle index 2832f06..f8c0113 100644 --- a/build.gradle +++ b/build.gradle @@ -82,14 +82,20 @@ def getVersion = { -> } return stdout.toString().trim() } + +// The version that a build version publishes as, also from the "version.sh" +// script, which drops only the build number and keeps the branch's name whole. +def getPublishedVersion = { buildVersion -> + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'bash', 'version.sh', '--published', buildVersion + standardOutput = stdout + } + return stdout.toString().trim() +} version = getVersion() -// Drop the build component from version number and use that for -// publishing -ext.mavenVersion = version.split('\\.') -ext.mavenVersion[3] = ext.mavenVersion[3].replaceAll("[0-9]+-", "-") -ext.mavenVersion[3] = ext.mavenVersion[3].replaceAll("[0-9]+", "").trim() -ext.mavenVersion = ext.mavenVersion.join(".").replace(".-", "-").replaceAll('\\.$', "") +ext.mavenVersion = getPublishedVersion(version) sourceCompatibility = '1.8' targetCompatibility = '1.8' @@ -160,7 +166,9 @@ publishing { name = 'cloudsmith' def releasesRepoUrl = "https://api-g.cloudsmith.io/maven/cinchapi/platform/" def snapshotsRepoUrl = "https://api-g.cloudsmith.io/maven/cinchapi/platform-snapshots/" - url = mavenVersion.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + // Only a master build publishes a plain X.Y.Z, so every other version, + // such as a release candidate or a feature build, is a snapshot. + url = (mavenVersion ==~ /\d+\.\d+\.\d+/) ? releasesRepoUrl : snapshotsRepoUrl credentials { username = System.getenv('CLOUDSMITH_API_USER') password = System.getenv('CLOUDSMITH_API_KEY') diff --git a/version.sh b/version.sh index 324626f..dcece9f 100755 --- a/version.sh +++ b/version.sh @@ -1,101 +1,261 @@ -# Cinchapi Inc. CONFIDENTIAL -# Copyright (c) 2016 Cinchapi Inc. All Rights Reserved. +#!/usr/bin/env bash +# Copied to a repo's version.sh by .agents/setup.sh on every skills pull. # -# All information contained herein is, and remains the property of Cinchapi. -# The intellectual and technical concepts contained herein are proprietary to -# Cinchapi and may be covered by U.S. and Foreign Patents, patents in process, -# and are protected by trade secret or copyright law. Dissemination of this -# information or reproduction of this material is strictly forbidden unless -# prior written permission is obtained from Cinchapi. Access to the source code -# contained herein is hereby forbidden to anyone except current Cinchapi -# employees, managers or contractors who have executed Confidentiality and -# Non-disclosure agreements explicitly covering such access. +# Print the build version of the repo that holds this script, convert a build +# version to the version it publishes as, or set the repo's semantic version. # -# The copyright notice above does not evidence any actual or intended -# publication or disclosure of this source code, which includes information -# that is confidential and/or proprietary, and is a trade secret, of Cinchapi. +# Usage: +# version.sh [--semantic | --calendar] +# version.sh --published BUILD_VERSION +# version.sh [--semantic] X.Y.Z +# version.sh --help # -# ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC -# DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS WRITTEN -# CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE -# LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE -# CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS TO -# REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR -# SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. +# A repo with a .version file uses semantic versions, and any other repo uses +# calendar versions. --semantic or --calendar chooses the scheme instead. +# +# A build version is a base, a build number, and a suffix for the branch. A +# semantic base is the X.Y.Z in .version. Its build number is the epoch second +# when CONTAINER_BUILD is set, as it is in CI, and otherwise the next count in +# the repo's .counter. A calendar base is the day as YYYY.MM.DD, and its build +# number is the epoch second. The suffix depends on the branch: +# +# master no suffix 1.2.0.7 +# release/* -rc 1.2.0.7-rc +# develop -SNAPSHOT 1.2.0.7-SNAPSHOT +# any other its whole name 1.2.0.7-FEATURE-GH-60-FIX +# +# Any other branch's name is uppercased, with each run of other characters as +# one dash. It is cut only as far as keeps the build version within the 128 +# characters of a Docker tag. A detached checkout is named HEAD, and a build +# outside Git is named BUILD. The branch is the first of CIRCLE_BRANCH, +# GITHUB_HEAD_REF, GITHUB_REF_NAME, BRANCH_NAME, CI_COMMIT_BRANCH, and +# GIT_BRANCH that is set and not empty, or else Git's. +# +# --published prints the version that a build publishes as: the build version +# without its build number, except that a -rc build carries the number as +# -rc, so each release candidate is a distinct version. A version with no +# build number prints unchanged. It reads and writes no file, so it runs from +# anywhere, as tools/version too. +# +# X.Y.Z sets the version of a semantic repo. It first runs the repo's +# .on-version-set.sh script, if there is one, with bash from the repo's root, +# with the new and the old version as its arguments, so the repo can rewrite the +# other files that name its version. The script must be a bash script. The old +# version is empty when the repo had none. Then it writes .version and removes +# .counter. When the script fails, the set stops with the script's status: +# .version and .counter stay as they were, and the script's own edits stay in +# place. +# +# A build or a set works on the directory that holds the script, so it runs only +# as a repo's version.sh. Bad usage and bad input exit 2 before anything +# changes, and a failed .on-version-set.sh script exits with its own status. +# Needs bash 3.2, git, and stock macOS or Linux tools. +set -euo pipefail -################################################# -### Script to get or set the current version ### -################################################# +# Docker caps a tag at 128 characters, and builds tag images with their version. +readonly MAX_VERSION_LENGTH=128 +readonly RE_SEMANTIC='^[0-9]+\.[0-9]+\.[0-9]+$' +readonly RE_BUILD='^([0-9]+\.[0-9]+\.[0-9]+)\.([0-9]+)(-[^[:space:]]+)?$' +readonly RE_PUBLISHED='^[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?$' +readonly RE_COUNT='^[0-9]{1,15}$' -#Ensure that this script operates from the directory in which it resides -cd "$(dirname "$0")" +# Prints message $1 and exits 2. +die() { + printf 'version.sh: %s\n' "$1" >&2 + exit 2 +} -# Bootstrap Git's ability to give the branch, if necessary -git rev-parse --abbrev-ref HEAD > /dev/null 2>&1 -if [ $? -ne 0 ]; then - git commit --allow-empty -m "Bootstrapping version.sh" -fi +# Prints the usage from this script's header. +usage() { awk 'NR > 3 && !/^#/ { exit } NR > 3 { sub(/^# ?/, ""); print }' "$0"; } -BASE_VERSION_FILE=".version" -JENKINS_HOME="/opt/jenkins" -if [ -w $JENKINS_HOME ]; then - COUNTER_FILE="$JENKINS_HOME/.counter" -else - COUNTER_FILE=".counter" -fi +# Prints the version that build version $1 publishes as. A version with no build +# number prints unchanged, and input that is no version exits 2. +published_version() { + if [[ $1 =~ $RE_BUILD ]]; then + if [ "${BASH_REMATCH[3]:-}" = -rc ]; then + printf '%s-rc%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" + else + printf '%s%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]:-}" + fi + elif [[ $1 =~ $RE_PUBLISHED ]]; then + printf '%s\n' "$1" + else + die "'$1' is not a version" + fi +} + +# Prints the X.Y.Z in .version, or exits 2 when .version is missing or holds +# something else. +semantic_base() { + local base + if [ -f .version ]; then + base=$(tr -d '[:space:]' <.version) + else + die 'a semantic version needs a .version file' + fi + if [[ $base =~ $RE_SEMANTIC ]]; then + printf '%s\n' "$base" + else + die ".version must hold X.Y.Z, not '$base'" + fi +} -if [ -z "$1" ] ; then - VERSION=`cat $BASE_VERSION_FILE` - BRANCH=`git rev-parse --abbrev-ref HEAD` - COMMIT=`git rev-parse HEAD | cut -c1-10` +# Prints the next count in .counter and stores it there. A missing counter, or +# one that holds no count, starts again at 1. +next_count() { + local n="" + if [ -f .counter ]; then + n=$(tr -d '[:space:]' <.counter) + fi + if [[ $n =~ $RE_COUNT ]]; then + n=$((10#$n + 1)) + else + n=1 + fi + printf '%s\n' "$n" >.counter + printf '%s\n' "$n" +} - # Get counter value - if [ ! -f $COUNTER_FILE ] ; then - echo 0 > $COUNTER_FILE +# Prints the branch this build is for: the CI's, else Git's, with HEAD for a +# detached checkout, and nothing outside Git. +branch_name() { + local ci=${CIRCLE_BRANCH:-${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}} + ci=${ci:-${BRANCH_NAME:-${CI_COMMIT_BRANCH:-${GIT_BRANCH:-}}}} + if [ -n "$ci" ]; then + printf '%s\n' "$ci" + elif git rev-parse --git-dir >/dev/null 2>&1; then + git symbolic-ref --quiet --short HEAD || printf '%s\n' HEAD + else + printf '\n' fi - if [ -z ${CONTAINER_BUILD+x} ]; then - COUNTER=`cat $COUNTER_FILE` - ((COUNTER++)) +} + +# Prints the suffix that branch $1 gives a build version whose part before the +# suffix is $2 characters long. +suffix_for() { + local name room + case "$1" in + master) printf '\n' ;; + release/*) printf '%s\n' -rc ;; + develop) printf '%s\n' -SNAPSHOT ;; + *) + name=$(printf '%s\n' "$1" | LC_ALL=C tr '[:lower:]' '[:upper:]' | + LC_ALL=C sed -E 's/[^A-Z0-9]+/-/g; s/^-+//; s/-+$//') + name=${name:-BUILD} + room=$((MAX_VERSION_LENGTH - $2 - 1)) + name=$(printf '%s\n' "${name:0:$room}" | sed -E 's/-+$//') + printf -- '-%s\n' "$name" + ;; + esac +} + +# Prints the next build version of the repo in the current directory under +# scheme $1, semantic or calendar. +build_version() { + local base number stamp suffix + if [ "$1" = semantic ]; then + base=$(semantic_base) + if [ -n "${CONTAINER_BUILD+x}" ]; then + number=$(date +%s) + else + number=$(next_count) + fi else - # If the build is run in a container, the COUNTER_FILE will be wiped - # away on each build, so we'll use the current unix timestamp for the - # counter. - COUNTER=`date +%s` + # One reading of the clock keeps the day and the second from straddling + # midnight. + stamp=$(date +%Y.%m.%d.%s) + base=${stamp%.*} + number=${stamp##*.} + fi + suffix=$(suffix_for "$(branch_name)" $((${#base} + 1 + ${#number}))) + printf '%s.%s%s\n' "$base" "$number" "$suffix" +} + +# Sets the repo in the current directory to semantic version $1, after the +# repo's .on-version-set.sh script rewrites the other files that name it. Writes +# .version, removes .counter, and prints a confirmation. When the script fails, +# exits with its status and leaves .version and .counter as they were. +set_version() { + local old="" + if [ -f .version ]; then + old=$(tr -d '[:space:]' <.version) fi - echo $COUNTER > $COUNTER_FILE - VERSION=$VERSION.$COUNTER - case $BRANCH in - develop ) - EXTRA="-SNAPSHOT" + if [ -f .on-version-set.sh ]; then + bash ./.on-version-set.sh "$1" "$old" + fi + printf '%s\n' "$1" >.version + rm -f .counter + printf 'The version has been set to %s\n' "$1" +} + +scheme="" +publish="" +published="" +new="" +help="" +while [ $# -gt 0 ]; do + case "$1" in + --semantic|--calendar) + if [ -n "$scheme" ]; then + die 'give one of --semantic and --calendar' + else + scheme=${1#--} + shift + fi ;; - feature* ) - IFS='/' - PARTS=( $BRANCH ) - EXTRA="-${PARTS[1]}" - EXTRA=`echo $EXTRA | tr '[:lower:]' '[:upper:]'` + --published) + if [ $# -lt 2 ]; then + die '--published needs a build version' + else + publish=1 + published=$2 + shift 2 + fi ;; - release* ) - EXTRA="" + -h|--help) + help=1 + shift ;; - * ) - # At this point we do not need to refer - # to any commit hash since we'll have a - # tag that represents the overall release - EXTRA="" + -*) + die "unknown option $1" + ;; + *) + if [ -n "$new" ]; then + die 'give one version to set' + else + new=$1 + shift + fi ;; esac - echo $VERSION$EXTRA -else - NEW_VERSION=$1 - if [[ $NEW_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then - echo $NEW_VERSION > $BASE_VERSION_FILE - rm $COUNTER_FILE 2>/dev/null - sed -i '' -E "s/[0-9]+\.[0-9]+\.[0-9]+/$NEW_VERSION/g" README.md +done - echo "The version has been set to $NEW_VERSION" +if [ -n "$help" ]; then + usage +elif [ -n "$publish" ]; then + if [ -n "$scheme$new" ]; then + die '--published takes nothing else' + else + published_version "$published" + fi +elif [ "$(basename "$0")" != version.sh ]; then + die "run the repo's version.sh, which works on the directory that holds it" +else + cd "$(dirname "$0")" + if [ -z "$scheme" ]; then + if [ -f .version ]; then + scheme=semantic + else + scheme=calendar + fi + fi + if [ -z "$new" ]; then + build_version "$scheme" + elif ! [[ $new =~ $RE_SEMANTIC ]]; then + die "set a version as X.Y.Z, not '$new'" + elif [ "$scheme" != semantic ]; then + die 'only a semantic version can be set; --semantic X.Y.Z starts one' else - echo "Please specify a valid version .." - exit 1 + set_version "$new" fi fi -exit 0