Skip to content

Commit 48e0b45

Browse files
committed
ci: implement automated versioning and release workflow
This commit introduces an automated system for version management and release creation, streamlining the development and deployment process. Key Changes: - **Version Management:** - `version.properties` file: Centralizes version information (major, minor, patch, snapshot). - `VersionConfig.kt` in `buildSrc`: Reads `version.properties` to provide `versionName` and `versionCode` to the `app/build.gradle.kts` file. - `prepare-release.sh` script: - Updates `version.properties` for a release (patch, minor, or major) by removing the snapshot flag and incrementing version numbers as specified. - Validates that the current version is a snapshot before proceeding. - `prepare-next-dev.sh` script: - Increments the patch version and sets the `snapshot` flag to true in `version.properties` for the next development iteration. - Validates that the current version is a release version. - **GitHub Actions Workflow (`release.yml`):** - Triggered manually via `workflow_dispatch` with `release_type` (patch, minor, major) and optional `release_name` inputs. - **Release Job:** - Checks out the repository and configures Git. - Reads the current version from `version.properties`. - Validates that the current version is a snapshot. - Runs `prepare-release.sh` to update `version.properties` for the release. - Commits the `version.properties` file with a "chore: release vX.Y.Z" message. - Creates and pushes a Git tag (e.g., `vX.Y.Z`). - Runs `prepare-next-dev.sh` to update `version.properties` for the next development cycle. - Commits the `version.properties` file with a "chore: prepare next development iteration X.Y.Z-snapshot" message. - **Create GitHub Release Job (depends on `release` job):** - Fetches the latest tag created by the `release` job. - Generates release notes based on commits between the new tag and the previous tag. - Creates a GitHub Release with the tag, optional release name, and generated notes. - **Google Play Release Workflow (`google-play-release.yml`):** - Triggered when a new GitHub Release is `published` (instead of on tag push). - Added a validation step to ensure the release tag matches the version in `version.properties`. - The `releaseName` from the GitHub Release is now used for the Google Play release. - Removed the separate "Create GitHub Release" job as it's now handled by the `release.yml` workflow. - **Build Configuration:** - `app/build.gradle.kts`: Updated to use `VersionConfig.versionCode` and `VersionConfig.versionName`. - `buildSrc/build.gradle.kts`: Added for `kotlin-dsl` plugin. - **Gitignore:** - Added `.kotlin/` to `.gitignore`. This new system automates version bumping, tagging, and GitHub Release creation, ensuring consistency and reducing manual effort for releases.
1 parent f51d86d commit 48e0b45

9 files changed

Lines changed: 297 additions & 47 deletions

File tree

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
1-
name: Release - Google Play Internal Track
1+
name: Release - Google Play
22

33
on:
4-
push:
5-
tags:
6-
- 'v*'
4+
release:
5+
types: [ published ]
76

87
jobs:
98
publish-google-play:
109
name: Publish to Google Play Internal Track
1110
runs-on: ubuntu-latest
1211
steps:
1312
- uses: actions/checkout@v4
13+
- name: Validate release tag matches version
14+
run: |
15+
if [ ! -f "version.properties" ]; then
16+
echo "Warning: version.properties not found, skipping validation"
17+
exit 0
18+
fi
19+
20+
source version.properties
21+
EXPECTED_TAG="v${major}.${minor}.${patch}"
22+
23+
if [ "${{ github.event.release.tag_name }}" != "$EXPECTED_TAG" ]; then
24+
echo "Error: Release tag ${{ github.event.release.tag_name }} doesn't match version ${EXPECTED_TAG}"
25+
exit 1
26+
fi
27+
28+
echo "✓ Release tag ${{ github.event.release.tag_name }} matches version ${major}.${minor}.${patch}"
1429
- name: set up JDK 17
1530
uses: actions/setup-java@v4
1631
with:
@@ -43,44 +58,4 @@ jobs:
4358
packageName: com.sloy.sevibus
4459
releaseFiles: app/build/outputs/bundle/release/app-release.aab
4560
track: internal
46-
status: draft
47-
48-
create-github-release:
49-
name: Create GitHub Release
50-
runs-on: ubuntu-latest
51-
needs: publish-google-play
52-
steps:
53-
- uses: actions/checkout@v4
54-
with:
55-
fetch-depth: 0
56-
- name: Get previous tag
57-
id: prev-tag
58-
run: |
59-
git fetch --tags
60-
PREV_TAG=$(git tag --sort=-version:refname | sed -n '2p')
61-
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
62-
- name: Generate release notes
63-
id: release-notes
64-
run: |
65-
if [ -n "${{ steps.prev-tag.outputs.prev_tag }}" ]; then
66-
COMMITS=$(git log --pretty=format:"- %s (%h)" ${{ steps.prev-tag.outputs.prev_tag }}..${{ github.ref_name }})
67-
else
68-
COMMITS=$(git log --pretty=format:"- %s (%h)")
69-
fi
70-
echo "commits<<EOF" >> $GITHUB_OUTPUT
71-
echo "$COMMITS" >> $GITHUB_OUTPUT
72-
echo "EOF" >> $GITHUB_OUTPUT
73-
- name: Create GitHub Release
74-
uses: ncipollo/release-action@v1
75-
with:
76-
tag: ${{ github.ref_name }}
77-
name: Release ${{ github.ref_name }}
78-
body: |
79-
## Changes in this release:
80-
81-
${{ steps.release-notes.outputs.commits }}
82-
83-
---
84-
This release has been automatically deployed to Google Play Internal Track.
85-
draft: false
86-
prerelease: false
61+
releaseName: "${{ github.event.release.name }}"

.github/workflows/release.yml

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
name: Create Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
release_type:
7+
description: 'Type of release'
8+
required: true
9+
default: 'patch'
10+
type: choice
11+
options:
12+
- patch
13+
- minor
14+
- major
15+
release_name:
16+
description: 'Optional release name (e.g., "Bonobus alerts")'
17+
required: false
18+
type: string
19+
20+
jobs:
21+
release:
22+
name: Create Release
23+
runs-on: ubuntu-latest
24+
permissions:
25+
contents: write
26+
27+
steps:
28+
# 1. Setup
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
token: ${{ secrets.GITHUB_TOKEN }}
33+
fetch-depth: 0
34+
35+
- name: Configure Git
36+
run: |
37+
git config user.name "github-actions[bot]"
38+
git config user.email "github-actions[bot]@users.noreply.github.com"
39+
40+
# 2. Read current version
41+
- name: Read current version
42+
id: current_version
43+
run: |
44+
source version.properties
45+
echo "major=$major" >> $GITHUB_OUTPUT
46+
echo "minor=$minor" >> $GITHUB_OUTPUT
47+
echo "patch=$patch" >> $GITHUB_OUTPUT
48+
echo "snapshot=$snapshot" >> $GITHUB_OUTPUT
49+
echo "current=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT
50+
51+
# 3. Validate current state
52+
- name: Validate snapshot state
53+
run: |
54+
if [ "${{ steps.current_version.outputs.snapshot }}" != "true" ]; then
55+
echo "Error: Cannot release from non-snapshot version"
56+
exit 1
57+
fi
58+
59+
# 4. Prepare release version (remove snapshot)
60+
- name: Prepare release version
61+
id: release_version
62+
run: |
63+
./scripts/prepare-release.sh ${{ github.event.inputs.release_type }}
64+
source version.properties
65+
echo "version=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT
66+
67+
# 5. Commit release version
68+
- name: Commit release version
69+
uses: stefanzweifel/git-auto-commit-action@v5
70+
with:
71+
commit_message: "chore: release v${{ steps.release_version.outputs.version }}"
72+
file_pattern: version.properties
73+
74+
# 6. Create and push tag
75+
- name: Create release tag
76+
run: |
77+
git tag "v${{ steps.release_version.outputs.version }}"
78+
git push origin "v${{ steps.release_version.outputs.version }}"
79+
80+
# 7. Prepare next development version
81+
- name: Prepare next development version
82+
id: next_version
83+
run: |
84+
./scripts/prepare-next-dev.sh
85+
source version.properties
86+
echo "next_version=${major}.${minor}.${patch}-snapshot" >> $GITHUB_OUTPUT
87+
88+
# 8. Commit next development version
89+
- name: Commit next development version
90+
uses: stefanzweifel/git-auto-commit-action@v5
91+
with:
92+
commit_message: "chore: prepare next development iteration ${{ steps.next_version.outputs.next_version }}"
93+
file_pattern: version.properties
94+
95+
create-github-release:
96+
name: Create GitHub Release
97+
runs-on: ubuntu-latest
98+
needs: release
99+
permissions:
100+
contents: write
101+
102+
steps:
103+
- name: Checkout repository
104+
uses: actions/checkout@v4
105+
with:
106+
fetch-depth: 0
107+
108+
- name: Get release version from tag
109+
id: release_info
110+
run: |
111+
# Get the tag that was just created by the release job
112+
LATEST_TAG=$(git tag --sort=-version:refname | head -1)
113+
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
114+
echo "version=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
115+
116+
- name: Get previous tag for release notes
117+
id: prev-tag
118+
run: |
119+
git fetch --tags
120+
PREV_TAG=$(git tag --sort=-version:refname | grep -v "${{ steps.release_info.outputs.tag }}" | head -1)
121+
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
122+
123+
- name: Generate release notes
124+
id: release-notes
125+
run: |
126+
if [ -n "${{ steps.prev-tag.outputs.prev_tag }}" ]; then
127+
COMMITS=$(git log --pretty=format:"- %s (%h)" ${{ steps.prev-tag.outputs.prev_tag }}..${{ steps.release_info.outputs.tag }})
128+
else
129+
COMMITS=$(git log --pretty=format:"- %s (%h)" --max-count=10)
130+
fi
131+
echo "commits<<EOF" >> $GITHUB_OUTPUT
132+
echo "$COMMITS" >> $GITHUB_OUTPUT
133+
echo "EOF" >> $GITHUB_OUTPUT
134+
135+
- name: Create GitHub Release
136+
uses: ncipollo/release-action@v1
137+
with:
138+
tag: "${{ steps.release_info.outputs.tag }}"
139+
name: "${{ steps.release_info.outputs.tag }}${{ github.event.inputs.release_name && format(' - {0}', github.event.inputs.release_name) || '' }}"
140+
body: |
141+
## Changes in this release:
142+
143+
${{ steps.release-notes.outputs.commits }}
144+
145+
---
146+
This release will be automatically deployed to Google Play Internal Track.
147+
draft: false
148+
prerelease: false
149+

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ certs/release.keystore
2323
.claude
2424
CLAUDE.md
2525
GEMINI.md
26+
.kotlin/

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ android {
1818
applicationId = "com.sloy.sevibus"
1919
minSdk = 26
2020
targetSdk = 36
21-
versionCode = 106
22-
versionName = "5.3.0"
21+
versionCode = VersionConfig.versionCode
22+
versionName = VersionConfig.versionName
2323

2424
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2525
vectorDrawables {

buildSrc/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
plugins {
2+
`kotlin-dsl`
3+
}
4+
5+
repositories {
6+
gradlePluginPortal()
7+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import java.io.File
2+
import java.util.Properties
3+
4+
object VersionConfig {
5+
private val versionProperties = Properties().apply {
6+
val versionFile = File("version.properties")
7+
if (versionFile.exists()) {
8+
load(versionFile.inputStream())
9+
} else {
10+
throw IllegalStateException("version.properties file not found")
11+
}
12+
}
13+
14+
val major: Int = versionProperties.getProperty("major").toInt()
15+
val minor: Int = versionProperties.getProperty("minor").toInt()
16+
val patch: Int = versionProperties.getProperty("patch").toInt()
17+
val snapshot: Boolean = versionProperties.getProperty("snapshot").toBoolean()
18+
19+
val versionName: String = buildString {
20+
append("$major.$minor.$patch")
21+
if (snapshot) {
22+
append("-snapshot")
23+
}
24+
}
25+
26+
val versionCode: Int = run {
27+
val majorPart = major.toString().padStart(3, '0')
28+
val minorPart = minor.toString().padStart(3, '0')
29+
val patchPart = patch.toString().padStart(3, '0')
30+
val snapshotPart = if (snapshot) "0" else "1"
31+
32+
"$majorPart$minorPart$patchPart$snapshotPart".toInt()
33+
}
34+
}

scripts/prepare-next-dev.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
set -e
3+
4+
if [ ! -f "version.properties" ]; then
5+
echo "Error: version.properties file not found"
6+
exit 1
7+
fi
8+
9+
source version.properties
10+
11+
if [ "$snapshot" != "false" ]; then
12+
echo "Error: Not a release version (current: ${major}.${minor}.${patch})"
13+
exit 1
14+
fi
15+
16+
# Always bump patch and add snapshot for next development version
17+
next_patch=$((patch + 1))
18+
19+
# Write next development version
20+
cat > version.properties << EOF
21+
major=$major
22+
minor=$minor
23+
patch=$next_patch
24+
snapshot=true
25+
EOF
26+
27+
echo "Prepared next development version: ${major}.${minor}.${next_patch}-snapshot"

scripts/prepare-release.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
set -e
3+
4+
RELEASE_TYPE=${1:-patch}
5+
6+
# Read current version
7+
if [ ! -f "version.properties" ]; then
8+
echo "Error: version.properties file not found"
9+
exit 1
10+
fi
11+
12+
source version.properties
13+
14+
if [ "$snapshot" != "true" ]; then
15+
echo "Error: Not a snapshot version (current: ${major}.${minor}.${patch})"
16+
exit 1
17+
fi
18+
19+
# Calculate release version based on type
20+
case $RELEASE_TYPE in
21+
"patch")
22+
# Keep current version, just remove snapshot
23+
release_major=$major
24+
release_minor=$minor
25+
release_patch=$patch
26+
;;
27+
"minor")
28+
# Bump minor, reset patch, remove snapshot
29+
release_major=$major
30+
release_minor=$((minor + 1))
31+
release_patch=0
32+
;;
33+
"major")
34+
# Bump major, reset minor and patch, remove snapshot
35+
release_major=$((major + 1))
36+
release_minor=0
37+
release_patch=0
38+
;;
39+
*)
40+
echo "Error: Invalid release type: $RELEASE_TYPE. Must be one of: patch, minor, major"
41+
exit 1
42+
;;
43+
esac
44+
45+
# Write release version
46+
cat > version.properties << EOF
47+
major=$release_major
48+
minor=$release_minor
49+
patch=$release_patch
50+
snapshot=false
51+
EOF
52+
53+
echo "Prepared release version: ${release_major}.${release_minor}.${release_patch} (type: $RELEASE_TYPE)"

version.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
major=5
2+
minor=3
3+
patch=1
4+
snapshot=true

0 commit comments

Comments
 (0)