소프트웨어 개발에서 버전 관리와 릴리스 프로세스는 필수적이지만, 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 : 버전 정보에 의미를 담다

출처: devopsschool.com

2011년 9월, GitHub의 공동 창업자 Tom Preston-Werner는 Semantic Versioning(이하 SemVer) Spec 1.0.0을 공개했다. SemVer와 이전의 버저닝 규칙과의 분명한 차이점은 버전 정보 만으로도 이전 버전과의 호환성 여부를 판별할 수 있게 되었다는 것이다. 이는 자동화 관점에서 매우 중요하다. 왜냐하면 의존성 관리를 자동화할 수 있기 때문이다.

버전 증가 규칙

  1. MAJOR 버전
    • 이전 버전과 호환되지 않는 API 변경 시 증가
  2. MINOR 버전
    • 이전 버전과 호환되는 기능 추가 시 증가
    • PATCH를 0으로 초기화
  3. 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 버전을 올린다.
이런 식으로 자동화된 버저닝 & CHANGELOG를 얻을 수 있다.

근데 이제 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 의 기본 설정대로 구성하였을 때의 플로우는 다음과 같다.

Release Please 봇이 생성한 Release PR
  1. PR 생성(from 개발자) → 리뷰 후 Approve → main 브랜치에 머지
  2. Release PR 생성(from 봇) → main 브랜치에 머지
    • Release PR에는 version tag, CHANGELOG 등의 정보가 포함
    • 추가적으로 다른 필요한 동작을 설정할 수도 있음(e.g. docker registry push 등)
  3. 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)

release typedescription
dartA repository with a pubspec.yaml and a CHANGELOG.md
elixirAn elixir repository with a mix.exs and a CHANGELOG.md
expoAn Expo based React Native repository, with a package.json, app.json and CHANGELOG.md
goGo repository, with a CHANGELOG.md
helmA helm chart repository with a Chart.yaml and a CHANGELOG.md
javaA strategy that generates SNAPSHOT version after each release
krm-blueprintA kpt package, with 1 or more KRM files and a CHANGELOG.md
mavenStrategy for Maven projects, generates SNAPSHOT version after each release and updates pom.xml automatically
nodeA Node.js repository, with a package.json and CHANGELOG.md
ocamlAn OCaml repository, containing 1 or more opam or esy files and a CHANGELOG.md
pythonA Python repository, with a setup.py, setup.cfg, version.py and CHANGELOG.md and optionally a pyproject.toml and a <project>/__init__.py
phpA php composer package with composer.json and CHANGELOG.md
rubyA Ruby repository, with version.rb and CHANGELOG.md
rustA Rust repository, with a Cargo.toml (either as a crate or workspace) and a CHANGELOG.md
sfdxA repository with a sfdx-project.json and a CHANGELOG.md
simpleA repository with a version.txt and a CHANGELOG.md
terraform-moduleA terraform module, with a version in the README.md, and a CHANGELOG.md

Releas Please는 잘 만들어진 도구답게, 워크플로우 단계의 꽤 많은 것들을 커스터마이즈할 수 있다.
하지만 기본적인 워크플로우 및 제공되는 기능으로도 충분하다.

결론

지금까지 소프트웨어 릴리즈 및 버저닝을 위한 SemVer와 Conventional Commit을 알아보고, 이를 통해 릴리즈 관리를 자동화할 수 있는 도구인 Release Please 를 알아보았다.
프로덕션 레벨로 소프트웨어 릴리즈 관리를 하고 싶은 분들에게 도움이 되었으면 한다.

댓글 남기기

인기 검색어

01010011에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기