Go 패키지 생성에서 버전관리 까지
GitHub 에서의 go 프로젝트 생성과 versioning 과정을 코드로 정리해 보고자 한다.
유의적 버전 2.0.0 | Semantic Versioning
https://github.com/golang-standards/project-layout
Semantic 버전관리
Go에서는 패키지 버저닝을 위해 Semantic Versioning을 사용하길 제안하고 있으며, 간단하게 정리를 하자면 다음과 같다.
- ~기존 버전과 비호환~ 되며 API가 바뀌면 “MAJOR 버전”을 올리고,
- ~기존 버전과 호환~ 되면서 ~새로운 기능을 추가~ 할 때는 “MINOR 버전”을 올리고,
- ~기존 버전과 호환~ 되면서 ~버그를 수정~ 한 것이라면 “PATCH 버전”을 올린다.
프로젝트 레이아웃 생성
go 프로젝트에 공식적으로 잡힌 표준은 없으나 커뮤니티에서 잡힌 레이아웃은 존재하는 모양이다. 별 다른 제안이 없어서 GitHub에 공유된 GitHub - Standard Go Project Layout 프로젝트 레이아웃을 참고해서 사용하고 있고, 몇 가지는 내 입맛에 맞게 사용하고 있다.
아래 링크는 GopherCon 2018에서 발표된 Go 애플리케이션 구조에 대한 발표이고 간단하고 쉽게 참고할 만해서 적어둔다.
* [YouTube - "How Do You Structure Your Go Apps"](https://www.youtube.com/watch?v=oL6JBUk6tj0)
* [PDF - "How Do You Structure Your Go Apps"](https://github.com/katzien/talks/blob/master/how-do-you-structure-your-go-apps/londongophers-2018-09-19/slides.pdf)
Go 애플리케이션 설계에 대한 번역글
번역 Go에서 애플리케이션 설계하기 · mingrammer's note
Github 에서 Go 패키지 버전관리 하기
vgo modules 가 1.11.1 에 go mod 내부에 도입 된 이후에, modules 가 패키지 버전을 인식하는 방식에 대해 알아보자.
아래는 go modules 버전관리 방법을 가장 간단하게 나타낸 이미지이며, Defining Go Modules 문서를 참고했다.
간단하게 설명하면 Major 버전 2.x 대가 되기전까지는 master 에서 tag 를 새로 따서 배포를 하고, 2.x 대가 넘어가게 되는 경우에는 v2 라는 이름으로 새로운 브랜치를 생성한 후에 go.mod 의 모듈패스에 /v2 와 같이 새로운 버전을 표기 하고 태그로 배포를 하는 형식이다.
다시 정리하면 버전 2.x 전까지는 master 에서 tag 로 배포, 2.x 이후 부터는 Major 버전 별로 브랜치를 생성해서 관리 하도록 한다.
hello 패키지 생성
이해를 돕기 위해 hello
라는 패키지를 Github 에 생성하고 배포하는 것을 가정하여 작성해봤다.
/* hello 패키지 초기화 */
$ go mod init github.com/breezymind/hello
go: creating new go.mod: module github.com/breezymind/hello
첫 hello v0.1.0 버전 릴리즈
/* 개발이 완료 된 이후에 첫 릴리즈 버전을 v0.1.0 이라고 가정하고 배포 */
/* 로컬 태그 생성과 메시지 */
$ git tag -a v0.1.0 -m'v0.1.0 release~!!'
/* 로컬 태그 삭제는 git tag -d v0.1.0 */
/* 태그 목록 확인 */
$ git tag -l
v0.1.0
/* 원격 서버에 태그 생성 */
$ git push origin v0.1.0
/* 원격 서버에서 태그 삭제는 git push origin :v0.1.0 */
v2 대 이하에서는 go 소스코드에서 import 도 아래와 같이 그대로 작성하면 된다.
import github.com/breezymind/hello
v2.x 릴리즈를 위한 브랜치 생성
v2.x 이상은 브랜치를 새로 생성하여 관리하도록 한다
/* 어느덧 v2.x 를 릴리즈해야 할 시점 */
/* 로컬 v2 브랜치 생성 */
$ git checkout -b v2
/* 로컬 브랜치 삭제는 git branch -d v2 */
v2.x 릴리즈를 위한 go.mod 수정
go.mod 상단을 아래와 같이 끝에 브랜치 버전 수정
module github.com/breezymind/hello/v2
v2.x 릴리즈 반영
아래와 같이 변경점 반영 후 v2.0.0 릴리즈
$ git add .
$ git commit -m'v2 브랜치 추가'
/* 원격 서버에 v2 브랜치 추가 */
$ git push origin v2
/* 원격 브랜치 삭제는 git push origin :v2 */
$ git tag -a v2.0.0 -m'v2.0.0 release~!!'
$ git push origin v2.0.0
위와 같이 v2 대 이상에서는 go 소스코드에서 아래와 같이 브랜치 버전을 같이 표기하여 import 하도록 한다.
import github.com/breezymind/hello/v2
버전 표기하여 실행파일 빌드하기
-ldflags 빌드옵션으로 바이너리 파일에 Version이나 빌드일시, Git Revision등을 전역 변수로 넘겨서, 실행 파일이 어떤 버전을 컴파일 한 것인지 표기하도록 해보자
아래는 실행시 간단한 예시이며, 실행 파일의 버전과 빌드일시, 리비전을 확인 할 수 있다.
$ ./bin/sg-cli-api.o --help
[STRAIT-GATE],
CLI-API server (ver.1.0.0, bld.201811131059, rev.8e59e02)
Usage of ./bin/sg-cli-api.o:
-mode string
running mode (default "docker")
다음은 Go 실행파일 내부에서 -ldflags 변수 받는 부분
package main
import (
"flag"
"fmt"
)
var (
Version, Build, Revision string
)
func init() {
fmt.Printf(
"\n[%s],\n%s (ver.%s, bld.%s, rev.%s)\n",
"STRAIT-GATE","CLI-API server",
Version, Build, Revision, // -ldflags -X 옵션으로 넘긴 값들
)
func main() {
...
}
다음은 shell script 또는 Makefile 에서 빌드할때 예시이다.
GOBASE=$(shell pwd)
GOPATH=$(GOBASE)/vendor
GOBIN=$(GOBASE)/bin
// 현재 년월일시분
BUILD=`date +%Y%m%d%H%M`
// 커밋 리비젼
REVISION=$(shell git rev-parse --short HEAD)
// LAST_VER 라는 변수는 현재 브랜치의 최신 태그 버전을 받음
LAST_VER=$(shell git describe --tags $(git rev-list --tags --max-count=1))
// 현재 브랜치 명을 같이 추가하고 싶을때는 아래 ldflags 에 추가하여 넘김
BRANCH=$(shell git rev-parse --abbrev-ref --verify HEAD)
...
// -ldflags -X로 전역변수 값을 넘김
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -mod vendor -ldflags "-X main.Version=$(LAST_VER) -X main.Build=$(BUILD) -X main.Revision=$(REVISION)" -o $(GOBIN)/$(app) $(GOBASE)/cmd/$(app)/$(app).go