From 7dd39a999468074c764ee65a923347f0d36874a9 Mon Sep 17 00:00:00 2001 From: Rick Barenthin Date: Wed, 1 Jun 2022 23:23:21 +0200 Subject: [PATCH] feat: add version and changelog update to pipeline This adds a pipeline step to manage versions and the changelog file. Reviewed-on: https://git.riba-interactive.de/rick/waitui/pulls/2 PR #2 --- .drone.yml | 59 ++++- .version.json | 5 + CHANGELOG.md | 8 + build/Dockerfile.build | 7 + build/conventi/Dockerfile | 13 ++ build/conventi/conventi.sh | 426 +++++++++++++++++++++++++++++++++++++ 6 files changed, 512 insertions(+), 6 deletions(-) create mode 100644 .version.json create mode 100644 CHANGELOG.md create mode 100644 build/Dockerfile.build create mode 100644 build/conventi/Dockerfile create mode 100755 build/conventi/conventi.sh diff --git a/.drone.yml b/.drone.yml index ca819f9..19eccc3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,26 +4,73 @@ type: docker name: main steps: - - name: Tag commit - image: alpine + - name: fetch tags + image: alpine/git commands: - - echo This would create a tag + - git fetch --tags + - name: update version and changelog + image: registry.riba-interactive.de/conventi:1 + - name: commit changelog updates + image: alpine/git + commands: + - git add . + - git commit -m "[CI SKIP] version and changelog update" + - name: tag commit + image: registry.riba-interactive.de/conventi:1 + commands: + - export version=$(conventi.sh get_version) + - git tag -am "Tagging new version $version" "$version" + - git push origin "$version" trigger: branch: - main + event: + - push --- kind: pipeline type: docker -name: develop +name: tag steps: - - name: Tag commit + - name: Build release + image: registry.riba-interactive.de/alpine-build:1 + commands: + - cmake -S. -Bcmake-build-ci -DCMAKE_BUILD_TYPE=Release + - cd cmake-build-ci + - cmake --build . + - echo "Done" + +trigger: + event: + - tag +--- +kind: pipeline +type: docker +name: Build and Test + +steps: + - name: Build and run tests + image: registry.riba-interactive.de/alpine-build:1 + commands: + - cmake -S. -Bcmake-build-ci -DCMAKE_BUILD_TYPE=Debug -DENABLE_TEST_COVERAGE=on + - cd cmake-build-ci + - cmake --build . + - ctest -T Test -T Coverage + + - name: Report test and coverage image: alpine commands: - - echo This would build the app and run test + - echo "Do some curl or so" + when: + event: + - pull_request trigger: branch: - develop + - feature/* + +image_pull_secrets: + - dockerconfig ... \ No newline at end of file diff --git a/.version.json b/.version.json new file mode 100644 index 0000000..a1aea80 --- /dev/null +++ b/.version.json @@ -0,0 +1,5 @@ +{ + "version": "0.0.0", + "sha": "", + "url": "https://git.riba-interactive.de/rick/waitui" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f7ecd09 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +---- + diff --git a/build/Dockerfile.build b/build/Dockerfile.build new file mode 100644 index 0000000..277cb5f --- /dev/null +++ b/build/Dockerfile.build @@ -0,0 +1,7 @@ +FROM alpine:3.16.0 + +RUN apk --no-cache add \ + bison \ + build-base \ + cmake \ + flex \ No newline at end of file diff --git a/build/conventi/Dockerfile b/build/conventi/Dockerfile new file mode 100644 index 0000000..c01d12f --- /dev/null +++ b/build/conventi/Dockerfile @@ -0,0 +1,13 @@ +FROM alpine:3.16.0 + +RUN apk --no-cache add \ + git \ + jq + +COPY conventi.sh /usr/local/bin/ + +VOLUME /conventi +WORKDIR /conventi + +ENTRYPOINT ["/usr/local/bin/conventi.sh"] +CMD [""] \ No newline at end of file diff --git a/build/conventi/conventi.sh b/build/conventi/conventi.sh new file mode 100755 index 0000000..cc9f527 --- /dev/null +++ b/build/conventi/conventi.sh @@ -0,0 +1,426 @@ +#!/usr/bin/env sh + +config_file=.version.json +changelog_file=CHANGELOG.md +changelog_header="# Changelog + +All notable changes to this project will be documented in this file. See [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit guidelines. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +---- +" + +reverse() { + if which tac >/dev/null 2>&1; then + tac + else + tail -r + fi +} + +generate_config() { + jq --null-input \ + --arg version "0.0.0" \ + --arg sha "" \ + --arg url "$1" \ + '{ "version": $version, "sha": $sha, "url": $url}' >$config_file +} + +update_config() { + # store in a var as reading and writing in one go to one file is not good + config=$(jq '.sha |= $sha | .version |= $version' --arg sha "$1" --arg version "$2" $config_file) + echo "$config" >$config_file +} + +get_config_version() { + jq '.version' <$config_file | tr -d '"' +} + +get_config_sha() { + jq '.sha' <$config_file | tr -d '"' +} + +get_config_url() { + jq '.url' <$config_file | tr -d '"' +} + +split_version_string() { + version_number=$(echo "$1" | cut -d'-' -f1 | cut -d'+' -f1) + pre_release=$(echo "$1" | cut -d'-' -f2 | cut -d'+' -f1) + build=$(echo "$1" | cut -d'-' -f2 | cut -d'+' -f2) + if [ "$pre_release" = "$1" ]; then + pre_release="" + fi + if [ "$build" = "$1" ] || [ "$build" = "$pre_release" ]; then + build="" + fi + + major=$(echo "$version_number" | cut -d'.' -f1) + minor=$(echo "$version_number" | cut -d'.' -f2) + patch=$(echo "$version_number" | cut -d'.' -f3) + pre_release_id=$(echo "$pre_release." | cut -d'.' -f2) + + echo "${major:-0} ${minor:-0} ${patch:-0} $build $pre_release_id" +} + +calculate_version() { + version_string=$(split_version_string "$1") + major=$(echo "$version_string" | cut -d' ' -f1) + minor=$(echo "$version_string" | cut -d' ' -f2) + patch=$(echo "$version_string" | cut -d' ' -f3) + pre_release_id=$(echo "$version_string" | cut -d' ' -f5) + pre_release="" + build="" + + if [ "$2" -ge 100 ]; then + major=$((major + 1)) + minor=0 + patch=0 + elif [ "$2" -ge 10 ]; then + minor=$((minor + 1)) + patch=0 + elif [ "$2" -ge 1 ]; then + patch=$((patch + 1)) + fi + + if [ "$3" = "rc" ]; then + if [ -z "$pre_release_identifier" ]; then + pre_release_identifier=1 + else + pre_release_identifier=$((pre_release_identifier + 1)) + fi + pre_release="rc.$pre_release_identifier" + fi + + if [ "$4" = "build" ]; then + build="$(git rev-parse --short HEAD)" + fi + + version_number="${major}.${minor}.${patch}" + if [ -n "$pre_release" ]; then + pre_release="-$pre_release" + fi + if [ -n "$build" ]; then + build="+$build" + fi + + echo "${version_number}${pre_release}${build}" +} + +get_git_url() { + url=$(git config --get remote.origin.url) + if ! echo "$url" | grep -q "^http"; then + url=$(echo "$url" | cut -d@ -f2 | sed 's/:/\//') + url="https://${url:-localhost}" + fi + + echo "$url" | sed 's/\.git$//' +} + +get_git_commits() { + since_hash=$(get_config_sha) + if [ -n "$since_hash" ]; then + git rev-list "$since_hash..HEAD" + else + git rev-list HEAD + fi +} + +get_git_commit_hash() { + git rev-parse --short "$1" +} + +get_git_first_commit_hash() { + git rev-list --max-parents=0 --abbrev-commit HEAD +} + +get_git_commit_subject() { + git show -s --format=%s "$1" +} + +get_git_commit_body() { + git show -s --format=%b "$1" +} + +get_commit_type() { + echo "$1" | sed -r -n 's/([a-z]+)(\(?|:?).*/\1/p' | tr '[:upper:]' '[:lower:]' +} + +get_commit_scope() { + echo "$1" | sed -r -n 's/[a-z]+\(([^)]+)\).*/\1/p' +} + +get_commit_description() { + echo "$1" | sed -r -n 's/.*:[ ]+(.*)/\1/p' +} + +get_commit_footer() { + with_colon=$(echo "$1" | sed -n '/^[A-Za-z-]\{1,\}: /,//{p;}') + with_hash=$(echo "$1" | sed -n '/^[A-Za-z-]\{1,\} #/,//{p;}') + fallback=$(echo "$1" | reverse | awk '/^$/{exit}1' | reverse) + + if [ -z "$with_colon" ] && [ -z "$with_hash" ]; then + echo "$fallback" + elif [ ${#with_colon} -gt ${#with_hash} ]; then + echo "$with_colon" + else + echo "$with_hash" + fi +} + +get_commit_body() { + pattern=$(get_commit_footer "$1" | head -n 1) + if [ -n "$pattern" ]; then + echo "$1" | sed -e "/$pattern/,\$d" + else + echo "$1" + fi +} + +is_breaking_change() { + if echo "$1" | grep -q '!' || echo "$2" | grep -q 'BREAKING[ -]CHANGE: '; then + echo 1 + else + echo 0 + fi +} + +get_breaking_change() { + with_colon=$(echo "$1" | sed -n '/^BREAKING[ -]CHANGE: /,/^[A-Za-z-]\{1,\}: /{ s/BREAKING[ -]CHANGE: \(.*\)/\1/; /^[A-Za-z-]\{1,\}: /!p;}') + with_hash=$(echo "$1" | sed -n '/^BREAKING[ -]CHANGE: /,/^[A-Za-z-]\{1,\} #/{ s/BREAKING[ -]CHANGE: \(.*\)/\1/; /^[A-Za-z-]\{1,\} #/!p;}') + if [ -n "$with_colon" ] && ! echo "$with_colon" | grep -q '[A-Za-z-]\{1,\} #'; then + echo "$with_colon" + else + echo "$with_hash" + fi +} + +format_entry_line() { + line="" + if [ -n "$2" ]; then + line="**$2:** " + fi + line="$line$1" + + echo "$line ([$3]($(get_config_url)/commit/$3))" | sed -e '2,$s/^[ ]*/ /' +} + +breaking_change_lines="" +feature_lines="" +bugfix_lines="" +build_lines="" +chore_lines="" +ci_lines="" +doc_lines="" +style_lines="" +refactor_lines="" +perf_lines="" +test_lines="" +revert_lines="" + +add_changelog_entry_line() { + case "$1" in + feat) + feature_lines=$(printf '%s\n* %s\n' "$feature_lines" "$2") + ;; + + fix) + bugfix_lines=$(printf '%s\n* %s\n' "$bugfix_lines" "$2") + ;; + + build) + build_lines=$(printf '%s\n* %s\n' "$build_lines" "$2") + ;; + + chore) + chore_lines=$(printf '%s\n* %s\n' "$chore_lines" "$2") + ;; + + ci) + ci_lines=$(printf '%s\n* %s\n' "$ci_lines" "$2") + ;; + + doc_lines) + doc_lines=$(printf '%s\n* %s\n' "$doc_lines" "$2") + ;; + + style) + style_lines=$(printf '%s\n* %s\n' "$style_lines" "$2") + ;; + + refactor) + refactor_lines=$(printf '%s\n* %s\n' "$refactor_lines" "$2") + ;; + + perf) + perf_lines=$(printf '%s\n* %s\n' "$perf_lines" "$2") + ;; + + test) + test_lines=$(printf '%s\n* %s\n' "$test_lines" "$2") + ;; + + revert) + revert_lines=$(printf '%s\n* %s\n' "$revert_lines" "$2") + ;; + esac +} + +format_entry() { + entry="" + if [ -n "$breaking_change_lines" ]; then + entry=$(printf '%s\n\n\n### BREAKING CHANGES ๐Ÿšจ\n%s\n' "$entry" "$breaking_change_lines") + fi + if [ -n "$bugfix_lines" ]; then + entry=$(printf '%s\n\n\n### Bug fixes ๐Ÿฉน\n%s\n' "$entry" "$bugfix_lines") + fi + if [ -n "$feature_lines" ]; then + entry=$(printf '%s\n\n\n### Features ๐Ÿ“ฆ\n%s\n' "$entry" "$feature_lines") + fi + if [ -n "$revert_lines" ]; then + entry=$(printf '%s\n\n\n### Reverts ๐Ÿ”™\n%s\n' "$entry" "$revert_lines") + fi + if [ -n "$build_lines" ]; then + entry=$(printf '%s\n\n\n### Build System ๐Ÿ—\n%s\n' "$entry" "$build_lines") + fi + if [ -n "$chore_lines" ]; then + entry=$(printf '%s\n\n\n### Chores ๐Ÿงฝ\n%s\n' "$entry" "$chore_lines") + fi + if [ -n "$ci_lines" ]; then + entry=$(printf '%s\n\n\n### CIs โš™๏ธ\n%s\n' "$entry" "$ci_lines") + fi + if [ -n "$doc_lines" ]; then + entry=$(printf '%s\n\n\n### Docs ๐Ÿ“‘\n%s\n' "$entry" "$doc_lines") + fi + if [ -n "$test_lines" ]; then + entry=$(printf '%s\n\n\n### Tests ๐Ÿงช\n%s\n' "$entry" "$test_lines") + fi + if [ -n "$style_lines" ]; then + entry=$(printf '%s\n\n\n### Styles ๐Ÿ–ผ\n%s\n' "$entry" "$style_lines") + fi + if [ -n "$refactor_lines" ]; then + entry=$(printf '%s\n\n\n### Refactors ๐Ÿ› \n%s\n' "$entry" "$refactor_lines") + fi + if [ -n "$perf_lines" ]; then + entry=$(printf '%s\n\n\n### Performance ๐ŸŽ\n%s\n' "$entry" "$perf_lines") + fi + if [ -n "$entry" ]; then + entry=$(printf '## [%s](%s/compare/%s...%s) (%s)%s' "$version" "$(get_config_url)" "$from_hash" "$to_hash" "$(date '+%Y-%m-%d')" "$entry") + fi + echo "$entry" +} + +generate_changelog_entry() { + is_major=0 + is_minor=0 + is_patch=0 + + version=$(get_config_version) + from_hash=$(get_config_sha) + if [ -z "$from_hash" ]; then + from_hash=$(get_git_first_commit_hash) + fi + to_hash=$(get_git_commit_hash HEAD) + + for commit in $(get_git_commits); do + git_subject=$(get_git_commit_subject "$commit") + git_body=$(get_git_commit_body "$commit") + hash=$(get_git_commit_hash "$commit") + + type=$(get_commit_type "$git_subject") + scope=$(get_commit_scope "$git_subject") + description=$(get_commit_description "$git_subject") + body=$(get_commit_body "$git_body") + footer=$(get_commit_footer "$git_body") + breaking_change=$(get_breaking_change "$footer") + is_breaking=$(is_breaking_change "$git_subject" "$footer") + line=$(format_entry_line "$description" "$scope" "$hash") + + if [ "$is_breaking" -eq 1 ] && [ -z "$breaking_change" ]; then + if [ -n "$body" ]; then + breaking_change="$body" + else + breaking_change="$description" + fi + fi + + if [ "$is_breaking" -eq 1 ]; then + is_major=1 + breaking_change=$(echo "$breaking_change" | sed -e '2,$s/^[ ]*/ /') + breaking_change_lines=$(printf '%s\n* %s\n' "$breaking_change_lines" "$breaking_change") + fi + + case "$type" in + feat) + is_minor=1 + ;; + + fix) + is_patch=1 + ;; + esac + + add_changelog_entry_line "$type" "$line" + done + + version_update=$((is_major * 100 + is_minor * 10 + is_patch)) + if [ $version_update -gt 0 ]; then + version=$(calculate_version "$version" "$version_update") + else + # no version change, no need to generate changelog + return + fi + + update_config "$to_hash" "$version" + + format_entry +} + +new_changelog() { + echo "$changelog_header" >$changelog_file +} + +new_changelog_entry() { + entry=$(generate_changelog_entry) + if [ -z "$entry" ]; then + return + fi + + changelog=$(sed -n '/^----/,//{/^----/!p;}' <$changelog_file) + if [ -n "$changelog" ]; then + changelog=$(printf '%s\n%s\n%s' "$changelog_header" "$entry" "$changelog") + else + changelog=$(printf '%s\n%s' "$changelog_header" "$entry") + fi + echo "$changelog" >$changelog_file +} + +init() { + if [ "$1" = "--help" ]; then + echo "$(basename "$0") [--help] [init|get_version]" + exit 0 + elif [ "$1" = "init" ]; then + echo "Generating a config ... " + echo "We guessed that the url for git links will be: $(get_git_url)" + echo "Please change inside the '${config_file}' file if needed!" + generate_config "$(get_git_url)" + new_changelog + exit 0 + fi + + if [ ! -f "$config_file" ]; then + echo >&2 "ERROR: config file is missing to generate changelog entry" + exit 1 + fi + + if [ "$1" = "get_version" ]; then + get_config_version + exit 0 + fi + + echo "Using existing config to generate changelog entry" + new_changelog_entry +} + +init "$@"