flutter 커스텀 패키지 배포할 때 버전 넘버링(SemVer) 자동 업데이트&자동릴리즈&자동태그 by git action
버전 넘버링을 세다 보면 헷갈릴 소지가 있어서 github action 으로 자동화하였습니다.
보통 flutter 프로젝트의 버전은 pubspec.yaml 에서 “version: “ 으로 관리합니다.
그러나 저는 좀더 체계적면서 손을 안대어도 자동으로 릴리즈나 태그가 등록되도록 github workflows를 이용하기로 했습니다.
버전 관리로는 SemVer(=Semantic Versioning)을 사용하기로 했습니다. flutter에서도 사용하는 방식입니다.
그래서 태그나 릴리즈를 원하는 push마다 버전이 원하는 단위(major, minor, patch, build)에서 자동 증가하고 업데이트 되도록 하였습니다.
그러기 위해선 version.yaml 파일을 하나 만들고, 거기에 다음과 같이 이번 push에서 반영되길 바라는 정보들을 넣었습니다.
태그와 릴리즈는 서로 다른 기능인데, 태그가 릴리즈보다 더 개발중심으로 빈번하게 등록할 수 있는 개념입니다.
그래서 총 3가지 case를 나누었습니다.
- 태그, 릴리즈 둘 다 안하는 push: 이 경우는 버전 업데이트가 없음.
- 태그만 하는 push: 이 경우는 릴리즈 없이 태그만 생성되고, 버전 업데이트는 있음.
- 태그, 릴리즈 둘 다 하는 push: 이 경우는 릴리즈, 태그 둘 다 생성, 등록하고 버전 업데이트도 있음.
예를 들어, do_next_tag: true 이면 do_next_release에 상관없이 버전 업데이트는 꼭 해서 코드에 재반영하게 됩니다. 그리고 release 생성등록은 반드시 do_next_release: true 일때만 하게 되죠.
새로운 태그를 만들때의 버전 넘버링은 version_update 의 온오프 설정에서 true인 단위에서 업데이트를 합니다. 예를 들어, version_update:minor:true이고 이전에 생성된 태그에서 최근 넘버링이 1.2.0+2 이면 새로운 태그는 1.3.0+1 가 되는거죠.
1
2
3
4
5
6
7
8
9
10
11
12
git_action:
do_next_release: false
do_next_tag: true
# 태그 생성 시 버전 정보를 업데이트.
# build 넘버링은 항상 증가.
version_update:
major: false
minor: true
patch: false
폴더 구조는 다음과 같이 하면 됩니다. pubspec.lock 은 제외.
루트 디렉토리에 .github/workflows 안에 version-and-release.yml 파일을 다음과 같이 넣었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
name: Update pubspec version and Publish Release
on:
push:
branches:
- main # main 브랜치에 푸시될 때 실행
jobs:
update-version:
runs-on: ubuntu-latest
steps:
# 1. 코드 체크아웃
- name: Checkout repository
uses: actions/checkout@v3
# 2. Git 사용자 설정
- name: Configure Git user
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
# 3. version.yaml 버전 정보 추출
- name: Extract version_update from version.yaml
id: extract_version_update
run: |
# version_update 값을 불러오기, 기본값 false
MAJOR_UPDATE=$(grep 'major:' version.yaml | awk '{print $2}')
MINOR_UPDATE=$(grep 'minor:' version.yaml | awk '{print $2}')
PATCH_UPDATE=$(grep 'patch:' version.yaml | awk '{print $2}')
MAJOR_UPDATE=${MAJOR_UPDATE:-false}
MINOR_UPDATE=${MINOR_UPDATE:-false}
PATCH_UPDATE=${PATCH_UPDATE:-false}
# Extract git_action
DO_NEXT_RELEASE=$(grep 'do_next_release:' version.yaml | awk '{print $2}')
DO_NEXT_TAG=$(grep 'do_next_tag:' version.yaml | awk '{print $2}')
echo "Extracted version_update: major=$MAJOR_UPDATE, minor=$MINOR_UPDATE, patch=$PATCH_UPDATE"
echo "Extracted git_action: do_next_release=$DO_NEXT_RELEASE, do_next_tag=$DO_NEXT_TAG"
echo "major_update=$MAJOR_UPDATE" >> $GITHUB_ENV
echo "minor_update=$MINOR_UPDATE" >> $GITHUB_ENV
echo "patch_update=$PATCH_UPDATE" >> $GITHUB_ENV
echo "do_next_release=$DO_NEXT_RELEASE" >> $GITHUB_ENV
echo "do_next_tag=$DO_NEXT_TAG" >> $GITHUB_ENV
# 4. 최신 태그 가져오기
- name: Get the latest tag
id: get_latest_tag
if: ${{ env.do_next_tag == 'true' || env.do_next_release == 'true' }}
run: |
git fetch --tags
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "0.0.0+0")
LATEST_TAG=${LATEST_TAG#v} # 'v' 접두사 있으면 제거
echo "latest_tag=$LATEST_TAG" >> $GITHUB_ENV
# 5. 버전 비교 및 새 버전 결정
- name: Compare versions and determine new version
if: ${{ env.do_next_tag == 'true' || env.do_next_release == 'true' }}
id: compare_versions
run: |
# 현재 버전 정보 가져오기, 기본값 0
CURRENT_MAJOR=$(echo "${{ env.latest_tag }}" | cut -d '.' -f 1)
CURRENT_MINOR=$(echo "${{ env.latest_tag }}" | cut -d '.' -f 2)
CURRENT_PATCH=$(echo "${{ env.latest_tag }}" | cut -d '.' -f 3 | cut -d '+' -f 1)
CURRENT_BUILD=$(echo "${{ env.latest_tag }}" | cut -d '+' -f 2)
CURRENT_MAJOR=${CURRENT_MAJOR:-0}
CURRENT_MINOR=${CURRENT_MINOR:-0}
CURRENT_PATCH=${CURRENT_PATCH:-0}
CURRENT_BUILD=${CURRENT_BUILD:-0}
# version_update 값 가져오기
MAJOR_UPDATE=${{ env.major_update }}
MINOR_UPDATE=${{ env.minor_update }}
PATCH_UPDATE=${{ env.patch_update }}
# 상위 단위부터 update=true인지 확인하고 처리
if [ "$MAJOR_UPDATE" = "true" ]; then
NEW_MAJOR=$((CURRENT_MAJOR + 1))
NEW_MINOR=0
NEW_PATCH=0
NEW_BUILD=1
elif [ "$MINOR_UPDATE" = "true" ]; then
NEW_MAJOR=$CURRENT_MAJOR
NEW_MINOR=$((CURRENT_MINOR + 1))
NEW_PATCH=0
NEW_BUILD=1
elif [ "$PATCH_UPDATE" = "true" ]; then
NEW_MAJOR=$CURRENT_MAJOR
NEW_MINOR=$CURRENT_MINOR
NEW_PATCH=$((CURRENT_PATCH + 1))
NEW_BUILD=1
else
# 모두 false일 경우 build만 증가
NEW_MAJOR=$CURRENT_MAJOR
NEW_MINOR=$CURRENT_MINOR
NEW_PATCH=$CURRENT_PATCH
NEW_BUILD=$((CURRENT_BUILD + 1))
fi
echo "new_version=$NEW_MAJOR.$NEW_MINOR.$NEW_PATCH+$NEW_BUILD" >> $GITHUB_ENV
echo "new_major=$NEW_MAJOR" >> $GITHUB_ENV
echo "new_minor=$NEW_MINOR" >> $GITHUB_ENV
echo "new_patch=$NEW_PATCH" >> $GITHUB_ENV
echo "new_build=$NEW_BUILD" >> $GITHUB_ENV
# 6. pubspec.yaml 업데이트, version.yaml 업데이트 및 커밋, 푸시
- name: Update pubspec.yaml and version.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 6. pubspec.yaml 업데이트, version.yaml 업데이트 및 커밋, 푸시
- name: Update pubspec.yaml and version.yaml
if: ${{ env.do_next_tag == 'true' || env.do_next_release == 'true' }}
run: |
# 업데이트할 새로운 버전 생성
NEW_VERSION="${{ env.new_version }}"
MAJOR="${{ env.new_major }}"
MINOR="${{ env.new_minor }}"
PATCH="${{ env.new_patch }}"
BUILD="${{ env.new_build }}"
# pubspec.yaml 업데이트
sed -i "s/^version: .*/version: $NEW_VERSION/" pubspec.yaml
# version.yaml 업데이트
sed -i "s/^ major: .*/ major: false/" version.yaml
sed -i "s/^ minor: .*/ minor: false/" version.yaml
sed -i "s/^ patch: .*/ patch: false/" version.yaml
sed -i "s/^ do_next_release: .*/ do_next_release: false/" version.yaml
sed -i "s/^ do_next_tag: .*/ do_next_tag: false/" version.yaml
# 업데이트 내용 커밋 및 푸시
git add pubspec.yaml version.yaml
git commit -m "Update version to $NEW_VERSION"
git push origin main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 7. 태그 생성 및 푸시
- name: Create tag if needed
if: ${{ env.do_next_tag == 'true' || env.do_next_release == 'true' }}
run: |
git tag ${{ env.new_version }}
git push origin ${{ env.new_version }}
# 8. 릴리스 생성
- name: Create Release if needed
if: ${{ env.do_next_release == 'true' }}
uses: actions/create-release@v1
with:
tag_name: ${{ env.new_version }}
release_name: ${{ env.new_version }}
body: |
Release notes for version ${{ env.new_version }}.
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
깃액션 명령어에 set-output 으로 변수 설정하는 법도 있는데, 이 방법은 deprecated 되고 추후 없어질 수도 있다고 했습니다. 그리고 GITHUB_ENV 으로 변수를 설정하는 법이 더 최근에 권장된다고 하여 해당 방법을 사용했습니다. 아쉬운 점은 vscode에서 ide 기능으로는 해당 변수 설정법이 등록이 안되어 있는지 “Context access might be invalid” 경고가 나타났습니다만 이건 기능엔 이상없는 ide 문제라고 합니다. ㅎㅎ
루트 디렉토리에 또 version.yaml을 넣어줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
git_action:
do_next_release: false
do_next_tag: false
# 태그 생성 시 버전 정보를 업데이트.
# build 넘버링은 항상 증가.
version_update:
major: false
minor: false
patch: false
do_next_tag 등을 true로 하고 main브랜치로 push하면, 해당 push 이후, version_update 여부를 참고해서 새로운 버전 넘버링을 정하고 태그를 푸시합니다.
다시 do_next_tag와 그 외 version_update들을 false로 초기화해서 다시 push를 해서 version코드가 초기화되도록 했습니다.
pubspec.yaml의 version도 당연히 자동으로 반영되게 해놓았습니다.


