Skip to content

Commit

Permalink
Added release script
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlesDuboisSAP committed Sep 19, 2024
1 parent 3cac67b commit 4a1603a
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 0 deletions.
177 changes: 177 additions & 0 deletions .github/workflows/prepare-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
name: "Prepare Release"

on:
workflow_dispatch:
inputs:
branch:
description: "The Branch to Release From"
required: false
default: "main"
release-version:
description: "The Version to Release"
required: false

jobs:
bump-version:
name: "Bump Version"
outputs:
current-version: ${{ steps.determine-versions.outputs.CURRENT_SNAPSHOT }}
release-version: ${{ steps.determine-versions.outputs.RELEASE_VERSION }}
new-version: ${{ steps.determine-versions.outputs.NEW_SNAPSHOT }}
release-branch: ${{ steps.prepare-release.outputs.BRANCH_NAME }}
release-commit: ${{ steps.prepare-release.outputs.RELEASE_COMMIT_ID }}
release-tag: ${{ steps.prepare-release.outputs.TAG_NAME }}
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}

- name: "Determine Versions"
id: determine-versions
run: python .pipeline/scripts/get-release-versions.py
env:
INPUT_VERSION: ${{ github.event.inputs.release-version }}

- run: "echo Release Version: ${{ steps.determine-versions.outputs.RELEASE_VERSION }}"
- run: "echo Current Version: ${{ steps.determine-versions.outputs.CURRENT_SNAPSHOT }}"
- run: "echo New Version: ${{ steps.determine-versions.outputs.NEW_SNAPSHOT }}"

- name: "Set Release Version to ${{ steps.determine-versions.outputs.RELEASE_VERSION }}"
id: prepare-release
run: |
# NOTE: If you change this pattern here, also adjust perform_release.yml:
BRANCH_NAME=RELEASE-${{ steps.determine-versions.outputs.RELEASE_VERSION }}
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
git switch --create $BRANCH_NAME
python .pipeline/scripts/set-release-versions.py --version ${{ steps.determine-versions.outputs.RELEASE_VERSION }}
git add .
git commit -m "Update to version ${{ steps.determine-versions.outputs.RELEASE_VERSION }}"
# We need to get the commit id, and push the branch so the release tag will point at the right commit afterwards
RELEASE_COMMIT_ID=$(git log -1 --pretty=format:"%H")
echo "RELEASE_COMMIT_ID=$RELEASE_COMMIT_ID" >> $GITHUB_OUTPUT
TAG_NAME=rel/${{ steps.determine-versions.outputs.RELEASE_VERSION }}
git tag $TAG_NAME $RELEASE_COMMIT_ID
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_OUTPUT
git push origin $BRANCH_NAME
git push origin $TAG_NAME
create-release:
name: "Create GitHub Release"
needs: [ bump-version, run-ci ]
outputs:
release-name: ${{ steps.create-release.outputs.RELEASE_NAME }}
release-url: ${{ steps.create-release.outputs.RELEASE_URL }}
permissions:
contents: write # needed to create a new release
actions: read # needed to download the artifacts from the CI workflow
runs-on: ubuntu-latest
steps:
- name: "Checkout repository"
uses: actions/checkout@v4
with:
ref: ${{ needs.bump-version.outputs.release-branch }}

- name: "Setup java"
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'

- name: "Build SDK"
run: |
MVN_ARGS="${{ env.MVN_MULTI_THREADED_ARGS }} clean install -DskipTests -DskipFormatting"
mvn $MVN_ARGS
- name: "Create Release"
id: create-release
run: |
tar -caf apidocs-${{ needs.bump-version.outputs.release-version }}.tar.gz -C .targets/target/reports/apidocs/ .
tar -caf release-${{ needs.bump-version.outputs.release-version }}.tar.gz -C .release-artifacts/ .
RELEASE_NAME="rel/${{ needs.bump-version.outputs.release-version }}"
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_OUTPUT
RELEASE_URL=$(gh release create "$RELEASE_NAME" \
--target ${{ needs.bump-version.outputs.release-commit }} \
--title "Release ${{ needs.bump-version.outputs.release-version }}" \
--draft --generate-notes \
apidocs-${{ needs.bump-version.outputs.release-version }}.tar.gz \
release-${{ needs.bump-version.outputs.release-version }}.tar.gz)
echo "RELEASE_URL=$RELEASE_URL" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ github.token }}

create-code-pr:
name: "Create Code PR"
needs: [ bump-version, run-ci, create-release, create-docs-pr, create-release-notes-pr ]
outputs:
pr-url: ${{ steps.create-code-pr.outputs.PR_URL }}
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
ref: ${{ needs.bump-version.outputs.release-branch }}

- name: "Set New Version"
run: |
python .pipeline/scripts/set-release-versions.py --version ${{ needs.bump-version.outputs.new-version }}
git add .
git commit -m "Update to version ${{ needs.bump-version.outputs.new-version }}"
git push
- name: "Create Code PR"
run: |
COMMIT_URL=${{ github.event.repository.html_url }}/commit/${{ needs.bump-version.outputs.release-commit }}
PR_URL=$(gh pr create --title "Release ${{ needs.bump-version.outputs.release-version }}" --body "## TODOs
- [ ] Review the changes in [the release commit]($COMMIT_URL)
- [ ] Update the Release notes
- [ ] Review the [Draft Release](${{ needs.create-release.outputs.release-url }})
- [ ] Review **and approve** this PR
- [ ] Trigger the [Perform Release Workflow](${{ github.event.repository.html_url }}/actions/workflows/perform-release.yml)
- [ ] Once the `Perform Release` workflow is through, head over to the [OSS Repository](https://oss.sonatype.org/#stagingRepositories) and log in with the credentials in the Team Password Safe. There should be a staged release. Check whether everything looks good and, if so, publish the release.")
echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT
env:
# GH_TOKEN: ${{ secrets.BOT_SDK_JS_FOR_DOCS_REPO_PR }}

handle-failure:
runs-on: ubuntu-latest
needs: [ bump-version, run-ci, create-release, create-docs-pr, create-release-notes-pr, create-code-pr ]
permissions:
contents: write # needed to delete the GitHub release
if: ${{ failure() }}
steps:
- name: "Checkout Repository"
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}

- name: "Delete Release"
if: ${{ needs.create-release.outputs.release-url != '' }}
run: gh release delete --repo "${{ github.repository }}" ${{ needs.create-release.outputs.release-name }} --yes
env:
GH_TOKEN: ${{ secrets.BOT_SDK_JS_FOR_DOCS_REPO_PR }}
continue-on-error: true

- name: "Delete Release Branch"
if: ${{ needs.bump-version.outputs.release-branch != '' }}
run: git push --delete origin ${{ needs.bump-version.outputs.release-branch }}
env:
GITHUB_TOKEN: ${{ secrets.BOT_SDK_JS_FOR_DOCS_REPO_PR }}
continue-on-error: true

- name: "Delete Release Tag"
if: ${{ needs.bump-version.outputs.release-tag != '' }}
run: git push --delete origin ${{ needs.bump-version.outputs.release-tag }}
env:
GITHUB_TOKEN: ${{ secrets.BOT_SDK_JS_FOR_DOCS_REPO_PR }}
continue-on-error: true
50 changes: 50 additions & 0 deletions .pipeline/scripts/get-release-versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
import re
import json
import subprocess
import unittest

def determine_versions(current_snapshot, input_version):

milestone_version_regex = r'^\d+\.\d+\.\d+-M\d+$'

if not input_version:
input_version = re.sub(r'-SNAPSHOT', '', current_snapshot)
elif re.match(milestone_version_regex, input_version):
return {'input_version': input_version, 'current_snapshot': current_snapshot, 'new_snapshot_version': current_snapshot}

version_parts = [int(x) for x in input_version.split('.')]
new_snapshot_version = f"{version_parts[0]}.{version_parts[1] + 1}.0-SNAPSHOT"

return {'input_version': input_version, 'current_snapshot': current_snapshot, 'new_snapshot_version': new_snapshot_version}

class TestDetermineVersions(unittest.TestCase):

def test_no_input_provided(self):
self.assertEqual(determine_versions('1.2.3-SNAPSHOT', None),
{'input_version': '1.2.3', 'current_snapshot': '1.2.3-SNAPSHOT','new_snapshot_version': '1.3.0-SNAPSHOT'})

def test_input_provided(self):
self.assertEqual(determine_versions('1.2.3-SNAPSHOT', '2.1.0'),
{'input_version': '2.1.0', 'current_snapshot': '1.2.3-SNAPSHOT','new_snapshot_version': '2.2.0-SNAPSHOT'})

def test_input_milestone_provided(self):
self.assertEqual(determine_versions('1.2.3-SNAPSHOT', '1.2.3-M2'),
{'input_version': '1.2.3-M2', 'current_snapshot': '1.2.3-SNAPSHOT','new_snapshot_version': '1.2.3-SNAPSHOT'})

def write_versions_github_output():
with open("latest.json", "r") as file:
current_snapshot = json.load(file)["version"]

input_version = os.environ.get("INPUT_VERSION")

calculated_versions = determine_versions(current_snapshot, input_version)

github_output = os.environ.get("GITHUB_OUTPUT")
with open(github_output, "a") as f:
f.write(f"RELEASE_VERSION={calculated_versions['input_version']}\n")
f.write(f"CURRENT_SNAPSHOT={calculated_versions['current_snapshot']}\n")
f.write(f"NEW_SNAPSHOT={calculated_versions['new_snapshot_version']}\n")

if __name__ == '__main__':
write_versions_github_output()
90 changes: 90 additions & 0 deletions .pipeline/scripts/set-release-versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import argparse
import os
import re
import sys
from xml.etree import ElementTree

class _CommentedTreeBuilder(ElementTree.TreeBuilder):
def __init__(self, *args, **kwargs):
super(_CommentedTreeBuilder, self).__init__(*args, **kwargs)

def comment(self, data):
self.start(ElementTree.Comment, {})
self.data(data)
self.end(ElementTree.Comment)

def _update_readme(sdk_version):
_update_file("README.md", r'(maven_central-)(.+)(-blue\.svg)', r'\g<1>%s\g<3>' % sdk_version)
_update_file("README.md", r'(a:sdk-core%20AND%20v:)(.+\))', r'\g<1>%s)' % sdk_version)

def _update_pom_files(sdk_version):
_update_version_tags(sdk_version)

with open("latest.json", "w") as f:
f.write('{\n "version": "%s"\n}\n' % sdk_version)


def _update_version_tags(sdk_version):
for root, dirs, files in os.walk(os.getcwd()):
for file in files:
if file == "pom.xml":
_update_file(os.path.join(root, file), r'(<sdk\.version>)(.*?)(</sdk\.version>)', r'\g<1>%s\g<3>' % sdk_version)


def _update_version_tag(pom_file, sdk_version):
ElementTree.register_namespace("", "http://maven.apache.org/POM/4.0.0")

parser = ElementTree.XMLParser(target=_CommentedTreeBuilder())

try:
tree = ElementTree.parse(pom_file, parser=parser)
root = tree.getroot()
child_to_parent = {c: p for p in root.iter() for c in p}
except:
print(f"ERROR: Failed to parse POM file {pom_file}.")
raise

namespaces = {'xmlns': 'http://maven.apache.org/POM/4.0.0'}

version_tags = root.findall(".//xmlns:version", namespaces=namespaces)
project_version_tags = list(filter(lambda x: child_to_parent[x].tag.endswith("project"), version_tags))

if len(project_version_tags) == 1:
project_version_tags[0].text = sdk_version

parent_tags = root.findall(".//xmlns:parent", namespaces=namespaces)

if len(parent_tags) == 1:
parent_tags[0].find("xmlns:version", namespaces=namespaces).text = sdk_version

# not sure why, but tree.write() (as used in other functions) produces a differently formatted XML declaration...
# that's why we the file is written in this kind of hacky manner
with open(pom_file, "w") as f:
f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + ElementTree.tostring(root).decode() + "\n")

def _update_file(file_name, search_str, replace_str):
with open(file_name) as f:
result = f.read()
result = re.sub(search_str, replace_str, result)

with open(file_name, "w") as f:
f.write(result)

def set_sdk_version(full_version):
_update_pom_files(full_version)
if "SNAPSHOT" not in full_version:
_update_readme(full_version)

if __name__ == '__main__':
try:
parser = argparse.ArgumentParser(description='SAP Cloud SDK - Versioning script.')

parser.add_argument('--version',
metavar='VERSION',
help='The version to be set.', required=True)
args = parser.parse_args()

set_sdk_version(args.version)

except KeyboardInterrupt:
sys.exit(1)

0 comments on commit 4a1603a

Please sign in to comment.