소프트웨어 개발에서 버전 관리와 릴리스 프로세스는 필수적이지만, SW 엔지니어링이 충분히 성숙하지 못하였던 시절에 이는 꽤나 골치 아픈 작업으로 여겨졌다. Semantic Versioning 과 Conventional Commit는 널리 알려진 릴리즈와 버저닝 전략이기에 프로덕션 레벨의 소프트웨어 릴리즈 관리에 관심이 있다면 한 번 쯤 들어봤을 것이다. 이 글은 SemVer와 Conventional Commit 을 이용하여 CI 단계에서 릴리즈와 버저닝을 자동화하는 구글의 Release Please를 소개하고, GitHub Actions를 이용하여 프로젝트에 적용하는 방법을 소개한다.
뭔가 많이 바뀐 것 같으니 2.0이라고 할게요

소프트웨어 버저닝 체계가 갖춰지지 않았던 시절, 버전 넘버는 퍼스널 컬러처럼 그날의 개발자 기분에 따라 정성적으로 정해졌다.
뭔가 많이 바뀐 것 같은 느낌이 들면 2.0, 별로 한게 없다는 생각이 들어서 1.1,
바뀌긴 바꼈는데 임시로 떼운 코드라 넘버링을 하고 싶지 않으면 1.0-a,
근데 또 어떤 날은 버전명에 의미를 담고 싶어서 1.0-a-opengl-windows-x64,
이전 릴리즈랑 다르게 이름붙여야 해서 git hash를 곁들인 1.0-a-f917bc …

이런 ad-hoc 한 버전 정보는 사용자에게 ‘뭔가 바꼈구나’라는 것 외에는 어떤 유용함도 제공하지 않는다. ad-hoc versioning의 진짜 문제는 의존성을 관리할 때 발생한다. 1.0-a 를 1.0-opengl-f917bc 로 변경할 때 의존성 문제가 발생하는지 여부는 오롯이 라이브러리를 가져다 쓴 사용자가 책임져야 한다.
SemVer : 버전 정보에 의미를 담다

2011년 9월, GitHub의 공동 창업자 Tom Preston-Werner는 Semantic Versioning(이하 SemVer) Spec 1.0.0을 공개했다. SemVer와 이전의 버저닝 규칙과의 분명한 차이점은 버전 정보 만으로도 이전 버전과의 호환성 여부를 판별할 수 있게 되었다는 것이다. 이는 자동화 관점에서 매우 중요하다. 왜냐하면 의존성 관리를 자동화할 수 있기 때문이다.
버전 증가 규칙
- MAJOR 버전
- 이전 버전과 호환되지 않는 API 변경 시 증가
- MINOR 버전
- 이전 버전과 호환되는 기능 추가 시 증가
- PATCH를 0으로 초기화
- PATCH 버전
- 이전 버전과 호환되는 버그 수정 시 증가
MAJOR.MINOR.PATCH 규칙 외에도 Pre-Release 규칙(하이픈 -)이라던지 메타데이터 규칙(더하기+) 등 몇 가지 세세한 규칙이 있는데, 자세한 것은 SemVer 2 스펙을 확인하자.(링크: https://semver.org/lang/ko/)
Conventional Commit : 사람도 기계도 모두 이해할 수 있는 커밋 메세지

앞서 SemVer가 호환성에 영향을 주는 변경을 추적하는 규칙임을 설명하였는데, 현대 소프트웨어 개발에서 변경의 최소 단위는 commit이라 할 수 있다. 여러 개발자의 commit 이력이 모여 소프트웨어 릴리즈의 변경사항을 구성한다면, commit message에도 SemVer처럼 작성 규칙을 정해둘 수 있지 않을까?
Angular 프로젝트는 Change log를 자동 생성하기 위해 Commit Message Guidelines 를 제안하였고, 이는 Conventional Commit 표준에 큰 영향을 주었다.
<타입>[적용 범위(선택 사항)]: <설명>
< 빈 칸 >
[본문(선택 사항)]
<빈 칸>
[꼬리말(선택 사항)]
Angular 포맷에 많은 영향을 받은 탓인지, Angular 포맷과 Conventional Commit 의 커밋 메세지 구조는 거의 비슷하다. 메세지 헤더에 해당 커밋의 타입이 무엇인지( fix / feature / BREAKING CHANGE 등등…) 기술하고 공백 라인을 추가한 후, 구체적인 내용을 작성하는 구조이다. 경우에 따라 꼬리말(=footer)을 작성하거나 생략할 수 있다.
Conventional Commit 과 Angular 포맷의 결정적인 차이는 의무화되는 커밋 타입의 종류가 얼마나 많은지 여부이다. 상술하였듯, 커밋 메세지 포맷을 구조화하는 가장 큰 목적은 호환성에 영향을 주는 변경을 추적하기 위함이다. 따라서 다음 3가지 type 외에 나머지는 반드시 지켜야 하는 커밋 타입이 아니다.
- feat: (=MINOR)
- fix: (=PATCH)
- BREAKING CHANGE: (=MAJOR)
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123
프로젝트에 Conventional Commit 규칙을 도입하였을 때 가장 좋은 점은 릴리즈 버저닝과 Change log 생성을 자동화할 수 있다는 것이다.
PR을 머지하는 시점에 우리는 어떤 커밋 이력이 이번 릴리즈에 포함되어 있는지 정확히 파악할 수 있다.
- Commit log 헤더가 <fix:> 로 시작한다면, PATCH 버전을 올린다.
- Commit log 헤더가 <feature:> 로 시작한다면, MINOR 버전을 올린다.
- Commit log footer가 <BREAKING CHANGE:> 로 시작하거나 타입 뒤에 !표기가 있다면, MAJOR 버전을 올린다.


근데 이제 Release Please Actions를 곁들인…
많은 오픈소스 프로젝트에서 Conventional Commit 과 SemVer 규칙을 따르기 때문에 익숙한 내용이었을 것으로 생각한다. 필자가 소개하려는 도구는 Google에서 제공하는 Release Please이다. (링크: https://github.com/googleapis/release-please) Conventional Commit 규칙을 따르는 프로젝트의 CHANGELOG와 SemVer 규칙에 맞는 Bump up을 자동화해주는 유용한 도구다. 특히 GitHub Actions 와 함께 사용하면 프로덕션 레벨의 소프트웨어 릴리즈 관리를 자동화할 수 있다.
(공식적으로도 GitHub Actions를 추천한다. 하지만 CLI 와 GitHub App도 지원을 한다.)
Release Please 의 기본 설정대로 구성하였을 때의 플로우는 다음과 같다.

- PR 생성(from 개발자) → 리뷰 후 Approve → main 브랜치에 머지
- Release PR 생성(from 봇) → main 브랜치에 머지
- Release PR에는 version tag, CHANGELOG 등의 정보가 포함
- 추가적으로 다른 필요한 동작을 설정할 수도 있음(e.g. docker registry push 등)
- Release 생성 → Bump up 된 버전 tag 와 CHANGELOG 등을 바탕으로
특히, 잘 알려진 언어 / 패키지 매니저는 쓰기 편하도록 release-type을 지정하여 버전 정보를 관리할 수 있다.(e.g. nodejs를 쓰는 경우 package.json 정보도 함께 자동 갱신)
다음은 지원하는 언어 / release-type 목록이다.(링크: https://github.com/googleapis/release-please-action?tab=readme-ov-file#release-types-supported)
Releas Please는 잘 만들어진 도구답게, 워크플로우 단계의 꽤 많은 것들을 커스터마이즈할 수 있다.
하지만 기본적인 워크플로우 및 제공되는 기능으로도 충분하다.
결론
지금까지 소프트웨어 릴리즈 및 버저닝을 위한 SemVer와 Conventional Commit을 알아보고, 이를 통해 릴리즈 관리를 자동화할 수 있는 도구인 Release Please 를 알아보았다.
프로덕션 레벨로 소프트웨어 릴리즈 관리를 하고 싶은 분들에게 도움이 되었으면 한다.





댓글 남기기