모듈
모든 현대 프로그래밍 언어에는 각각 성숙한 종속성 관리 도구가 있습니다. 예를 들어 Java 의 Gradle, Python 의 Pip, NodeJs 의 Npm 등이 있습니다. 좋은 종속성 관리 도구는 개발자의 시간을 절약하고 개발 효율성을 높일 수 있습니다. 그러나 Go 는 초기에 성숙한 종속성 관리 솔루션이 없었습니다. 당시 모든 코드는 GOPATH 디렉토리에 저장되어 있어 프로젝트에 매우 부적합했으며 버전이 혼란스럽고 종속성 관리가 어려웠습니다. 이 문제를 해결하기 위해 주요 커뮤니티 개발자들이 다양한 의견을 제시했고 상황은 혼란스러웠습니다. 그 기간 동안 Vendor 와 같은 우수한 도구도 등장했지만, Go1.11 에서 공식적으로 Go Mod 라는 공식 종속성 관리 도구를 출시하여 이전의 혼란스러운 상황을 종식시켰고 이후 업데이트에서 지속적으로 개선하여 이전의 구식 도구를淘汰했습니다. 오늘날 이 글을 작성하는 시점에서 Go 버전은 이미 1.20 에 이르렀으며 거의 모든 Go 프로젝트가 Go Mod 를 사용하고 있습니다. 따라서 본 문서에서는 Go Mod 만 소개할 것입니다. 공식에서는 Go 모듈에 대해 매우 세심한 문서를 작성했습니다: Go Modules Reference.
모듈 작성
Go Module 은 본질적으로 VCS(버전 관리 시스템) 를 기반으로 합니다. 종속성을 다운로드할 때 실제로는 git 과 같은 VCS 명령을 실행하는 것입니다. 따라서 작성한 라이브러리를 공유하려면 다음 세 가지만 충족하면 됩니다.
- 소스 코드 저장소가 공개적으로 접근 가능하고 VCS 가 다음 중 하나여야 합니다.
- git
- hg (Mercurial)
- bzr (Bazaar)
- svn
- fossil
- 규격에 맞는 go mod 프로젝트여야 합니다.
- 의미적 버전 규격을 준수해야 합니다.
따라서 평소와 같이 VCS 를 사용하여 개발하고 특정 버전에 표준에 맞는 Tag 를 붙이면 다른 사람들이 모듈 이름을 통해 작성한 라이브러리를 다운로드할 수 있습니다. 아래에서는 예제를 통해 모듈 개발의 몇 가지 단계를 살펴보겠습니다.
예제 저장소: 246859/hello: say hello (github.com)
준비
시작하기 전에 Go Mod 를 완전히 지원할 수 있는 버전 (Go >= 1.17) 을 사용하고 Go Module 이 활성화되어 있는지 확인해야 합니다. 다음 명령을 통해 활성화 여부를 확인할 수 있습니다.
$ go env GO111MODULE활성화되지 않은 경우 다음 명령을 통해 Go Module 을 활성화합니다.
$ go env -w GO111MODULE=on생성
먼저 공개적으로 접근 가능한 소스 코드 저장소가 필요합니다. 선택지는 많지만 Github 을 추천합니다. Github 에서 새 프로젝트를 생성하고 이름을 hello 로 지정합니다. 저장소 이름에는 특별한 제한이 없지만 모듈 이름에 영향을 줄 수 있으므로 특수 문자를 사용하지 않는 것이 좋습니다.

생성이 완료되면 저장소 URL 이 https://github.com/246859/hello 임을 알 수 있으며, 이에 해당하는 Go 모듈 이름은 github.com/246859/hello 입니다.

그런 다음 이를 로컬에 클론하고 go mod init 명령을 통해 모듈을 초기화합니다.
$ git clone git@github.com:246859/hello.git
Cloning into 'hello'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (5/5), done.
$ cd hello && go mod init github.com/246859/hello
go: creating new go.mod: module github.com/246859/hello작성
그런 다음 개발 작업을 진행할 수 있습니다. 기능은 매우 간단하며 하나의 함수만 있습니다.
// hello.go
package hello
import "fmt"
// Hello returns hello message
func Hello(name string) string {
if name == "" {
name = "world"
}
return fmt.Sprintf("hello %s!", name)
}ついでに 테스트 파일을 작성하여 단위 테스트를 수행합니다.
// hello_test.go
package hello_test
import (
"testing"
"fmt"
"github.com/246859/hello"
)
func TestHello(t *testing.T) {
data := "jack"
expected := fmt.Sprintf("hello %s!", data)
result := hello.Hello(data)
if result != expected {
t.Fatalf("expected result %s, but got %s", expected, result)
}
}다음으로 hello 를 출력하는 명령줄 프로그램을 작성합니다. 기능은 역시 매우 간단합니다. 명령줄 프로그램의 경우 규격에 따라 프로젝트 cmd/app_name/ 에서 생성하므로 hello 명령줄 프로그램의 파일은 cmd/hello/ 디렉토리에 저장되며 관련 코드를 작성합니다.
// cmd/hello/main.go
package main
import (
"flag"
"github.com/246859/hello"
"os"
)
var name string
func init() {
flag.StringVar(&name, "name", "world", "name to say hello")
}
func main() {
flag.Parse()
msg := hello.Hello(name)
_, err := os.Stdout.WriteString(msg)
if err != nil {
os.Stderr.WriteString(err.Error())
}
}테스트
작성이 완료되면 소스 코드를 포맷팅하고 테스트합니다.
$ go fmt && go vet ./...
$ go test -v .
=== RUN TestHello
--- PASS: TestHello (0.00s)
PASS
ok github.com/246859/hello 0.023s명령줄 프로그램을 실행합니다.
$ go run ./cmd/hello -name jack
hello jack!문서
마지막으로 이 라이브러리를 위한 간결하고 명확한 README 를 작성하여 다른 개발자가 어떻게 사용하는지 한눈에 알 수 있도록 합니다.
# hello
just say hello
## Install
import code
```bash
go get github.com/246859/hello@latest
```
install cmd
```bash
go install github.com/246859/hello/cmd/hello@latest
```
## Example
Here's a simple example as follows:
```go
package main
import (
"fmt"
"github.com/246859/hello"
)
func main() {
result := hello.Hello("jack")
fmt.Println(result)
}
```이는 매우 간단한 README 문서이며 직접 풍부하게 작성할 수도 있습니다.
업로드
모든 코드가 작성되고 테스트가 완료되면 수정 사항을 커밋하여 원격 저장소에 푸시할 수 있습니다.
$ git add go.mod hello.go hello_test.go cmd/ example/ README.md
$ git commit -m "chore(mod): mod init" go.mod
[main 5087fa2] chore(mod): mod init
1 file changed, 3 insertions(+)
create mode 100644 go.mod
$ git commit -m "feat(hello): complete Hello func" hello.go
[main 099a8bf] feat(hello): complete Hello func
1 file changed, 11 insertions(+)
create mode 100644 hello.go
$ git commit -m "test(hello): complete hello testcase" hello_test.go
[main 76e8c1e] test(hello): complete hello testcase
1 file changed, 17 insertions(+)
create mode 100644 hello_test.go
$ git commit -m "feat(hello): complete hello cmd" cmd/hello/
[main a62a605] feat(hello): complete hello cmd
1 file changed, 22 insertions(+)
create mode 100644 cmd/hello/main.go
$ git commit -m "docs(example): add hello example" example/
[main 5c51ce4] docs(example): add hello example
1 file changed, 11 insertions(+)
create mode 100644 example/main.go
$ git commit -m "docs(README): update README" README.md
[main e6fbc62] docs(README): update README
1 file changed, 27 insertions(+), 1 deletion(-)총 6 개의 커밋은 많지 않으며 커밋 완료 후 최신 커밋에 태그를 생성합니다.
$ git tag v1.0.0
$ git tag -l
v1.0.0
$ git log --oneline
e6fbc62 (HEAD -> main, tag: v1.0.0, origin/main, origin/HEAD) docs(README): update README
5c51ce4 docs(example): add hello example
a62a605 feat(hello): complete hello cmd
76e8c1e test(hello): complete hello testcase
099a8bf feat(hello): complete Hello func
5087fa2 chore(mod): mod init
1f422d1 Initial commit마지막으로 원격 저장소에 푸시합니다.
$ git push --tags
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 16 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (21/21), 2.43 KiB | 1.22 MiB/s, done.
Total 21 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), done.
To github.com:246859/hello.git
1f422d1..e6fbc62 main -> main
* [new tag] v1.0.0 -> v1.0.0푸시가 완료되면 release 를 생성합니다 (태그가 하나면 충분하며 release 는 Github 규격에 맞출 뿐입니다).

이로써 모듈 작성이 완료되었습니다. 이상은 모듈 개발의 기본 흐름이며 다른 개발자들은 모듈 이름을 통해 코드를 가져오거나 명령줄 도구를 설치할 수 있습니다.
인용
go get 을 통해 라이브러리를 인용합니다.
$ go get github.com/246859/hello@latest
go: downloading github.com/246859/hello v1.0.0
go: added github.com/246859/hello v1.0.0go install 을 통해 명령줄 프로그램을 설치합니다.
$ go install github.com/246859/hello/cmd/hello@latest && hello -name jack
hello jack!또는 go run 을 사용하여 직접 실행합니다.
$ go run -mod=mod github.com/246859/hello/cmd/hello -name jack
hello jack!라이브러리가 인용되면 Go Package 는 자동으로 페이지를 생성하며 개발자가 별도로 작업을 수행할 필요가 없습니다. 예를 들어 hello 라이브러리에는 전용 문서 페이지가 있습니다 (아래 그림 참조).

모듈 업로드에 대한 자세한 내용은 Add a package 를 참조하십시오.
모듈 삭제에 대한 정보는 Removing a package 를 참조하십시오.
프록시 설정
Go 는 Maven Repo, PyPi, NPM 과 같은 중앙 저장소는 없지만 공식 프록시 저장소인 Go modules services (golang.org) 가 있습니다. 이는 버전 및 모듈 이름에 따라 개발자가 다운로드한 모듈을 캐시합니다. 하지만 서버가 해외에 배포되어 있어 국내 사용자의 접근 속도가 좋지 않으므로 기본 모듈 프록시 주소를 수정해야 합니다. 현재 국내에서 잘 구축된 곳은 다음과 같습니다.

여기서는 七牛云의 프록시를 선택하며 다음 명령을 실행하여 Go 프록시를 수정합니다. 여기서 direct 는 프록시 다운로드 실패 시 프록시 캐시를 우회하여 소스 코드 저장소에 직접 접근함을 나타냅니다.
$ go env -w GOPROXY=https://goproxy.cn,direct프록시 수정이 성공하면 이후 종속성 다운로드가 매우 빨라집니다.
종속성 다운로드
프록시를 수정한 후 서드파티 종속성을 설치해 보겠습니다. Go 공식에는 전용 종속성 조회 웹사이트가 있습니다: Go Packages.
코드 인용
안에서 유명한 Web 프레임워크 Gin 을 검색합니다.

여기에는 많은 검색 결과가 표시됩니다. 서드파티 종속성을 사용할 때는 인용 횟수와 업데이트 시간을 고려하여 해당 종속성 채택 여부를 결정해야 합니다. 여기서는 첫 번째를 선택합니다.

해당 페이지에 들어가면 이 종속성의 문서 페이지임을 알 수 있으며 이에 대한 매우 많은 상세 정보가 있으며 이후 문서를查阅할 때도 이곳을 이용할 수 있습니다.

여기서는 해당 주소를 복사한 후 이전에 생성한 프로젝트에서 go get 명령을 사용합니다. 명령은 다음과 같습니다.
$ go get github.com/gin-gonic/gin과정에서 많은 종속성이 다운로드되며 에러가 없으면 다운로드 성공입니다.
$ go get github.com/gin-gonic/gin
go: added github.com/bytedance/sonic v1.8.0
go: added github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311
go: added github.com/gin-contrib/sse v0.1.0
go: added github.com/gin-gonic/gin v1.9.0
go: added github.com/go-playground/locales v0.14.1
go: added github.com/go-playground/universal-translator v0.18.1
go: added github.com/go-playground/validator/v10 v10.11.2
go: added github.com/goccy/go-json v0.10.0
go: added github.com/json-iterator/go v1.1.12
go: added github.com/klauspost/cpuid/v2 v2.0.9
go: added github.com/leodido/go-urn v1.2.1
go: added github.com/mattn/go-isatty v0.0.17
go: added github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
go: added github.com/modern-go/reflect2 v1.0.2
go: added github.com/pelletier/go-toml/v2 v2.0.6
go: added github.com/twitchyliquid64/golang-asm v0.15.1
go: added github.com/ugorji/go/codec v1.2.9
go: added golang.org/x/arch v0.0.0-20210923205945-b76863e36670
go: added golang.org/x/crypto v0.5.0
go: added golang.org/x/net v0.7.0
go: added golang.org/x/sys v0.5.0
go: added golang.org/x/text v0.7.0
go: added google.golang.org/protobuf v1.28.1
go: added gopkg.in/yaml.v3 v3.0.1완료 후 go.mod 파일을 확인합니다.
$ cat go.mod
module golearn
go 1.20
require github.com/gin-gonic/gin v1.9.0
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)이전에 비해 많은 것이 추가되었고 디렉토리下に go.sum 이라는 파일이 새로 생겼음을 알 수 있습니다.
$ ls
go.mod go.sum main.go여기서는 일단 넘어가고 main.go 파일을 다음 코드로 수정합니다.
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
gin.Default().Run()
}프로젝트를 다시 실행합니다.
$ go run golearn
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080이로써 한 줄의 코드로 가장 간단한 Web 서버를 실행할 수 있었습니다. 더 이상 특정 종속성이 필요하지 않을 때도 go get 명령을 사용하여 해당 종속성을 삭제할 수 있습니다. 여기서는 Gin 을 삭제하는 예를 들어보겠습니다.
$ go get github.com/gin-gonic/gin@none
go: removed github.com/gin-gonic/gin v1.9.0종속성 주소 뒤에 @none 을 추가하면 해당 종속성을 삭제할 수 있으며 결과도 삭제 성공을 표시합니다. 이때 다시 go.mod 파일을 확인하면 Gin 종속성이 없어진 것을 알 수 있습니다.
$ cat go.mod | grep github.com/gin-gonic/gin최신 버전으로 업그레이드하려면 @latest 접미사를 추가하거나 사용 가능한 Release 버전 번호를 직접 조회할 수 있습니다.
$ go get -u github.com/gin-gonic/gin@latest명령줄 설치
go install 명령은 서드파티 종속성을 로컬에 다운로드하여 바이너리 파일로 컴파일합니다. Go 의 컴파일 속도로 인해 이 과정은 보통 많은 시간이 소요되지 않으며 Go 는 이를 $GOPATH/bin 또는 $GOBIN 디렉토리에 저장하여 전역에서 해당 바이너리 파일을 실행할 수 있도록 합니다 (해당 경로를 환경 변수에 추가했다는 전제하에).
TIP
install 명령을 사용할 때는 반드시 버전 번호를 지정해야 합니다.
예를 들어 Go 언어로 작성된 디버거 delve 를 다운로드합니다.
$ go install github.com/go-delve/delve/cmd/dlv@latest
go: downloading github.com/go-delve/delve v1.22.1
go: downloading github.com/cosiner/argv v0.1.0
go: downloading github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d
go: downloading github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62
go: downloading github.com/google/go-dap v0.11.0
go: downloading github.com/hashicorp/golang-lru v1.0.2
go: downloading golang.org/x/arch v0.6.0
go: downloading github.com/cpuguy83/go-md2man/v2 v2.0.2
go: downloading go.starlark.net v0.0.0-20231101134539-556fd59b42f6
go: downloading github.com/cilium/ebpf v0.11.0
go: downloading github.com/mattn/go-runewidth v0.0.13
go: downloading github.com/russross/blackfriday/v2 v2.1.0
go: downloading github.com/rivo/uniseg v0.2.0
go: downloading golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
$ dlv -v
Error: unknown shorthand flag: 'v' in -v
Usage:
dlv [command]
Available Commands:
attach Attach to running process and begin debugging.
completion Generate the autocompletion script for the specified shell
connect Connect to a headless debug server with a terminal client.
core Examine a core dump.
dap Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).
debug Compile and begin debugging main package in current directory, or the package specified.
exec Execute a precompiled binary, and begin a debug session.
help Help about any command
test Compile test binary and begin debugging program.
trace Compile and begin tracing program.
version Prints version.
Additional help topics:
dlv backend Help about the --backend flag.
dlv log Help about logging flags.
dlv redirect Help about file redirection.
Use "dlv [command] --help" for more information about a command.모듈 관리
위의 모든 내용은 Go Mod 의 기본 사용법만 설명했지만 사실 Go Mod 를 배우기에는 이것만으로는 전혀 부족하지 않습니다. 공식에서는 모듈을 다음과 같이 정의합니다: 버전으로 표시된 패키지 집합. 위의 정의에서 패키지는 이미 매우 친숙한 개념이며 버전은 의미적 버전 번호를 따라야 하며 형식은 v(major).(minor).(patch) 로 정의됩니다. 예를 들어 Go 버전 v1.20.1 에서 주 버전은 1, 부 버전은 20, 패치 버전은 1 이며 합치면 v1.20.1 이 됩니다. 아래는 더 자세한 설명입니다.
major: major 버전이 변경될 때는 프로젝트에 호환되지 않는 변경이 발생했음을 의미하며 구버전 프로젝트를 새 버전으로 업그레이드하면 대부분 정상적으로 실행되지 않습니다.minor:minor버전이 변경될 때는 프로젝트에 새로운 기능이 추가되었음을 의미하며 이전 버전의 기반에 새로운 기능만 추가된 것입니다.patch:patch버전이 변경될 때는 버그가 수정되었음을 의미하며 새로운 기능이 추가되지 않았습니다.
일반 명령어
| 명령어 | 설명 |
|---|---|
go mod download | 현재 프로젝트의 종속성 패키지 다운로드 |
go mod edit | go.mod 파일 편집 |
go mod graph | 모듈 종속성 그래프 출력 |
go mod init | 현재 디렉토리에서 go mod 초기화 |
go mod tidy | 프로젝트 모듈 정리 |
go mod verify | 프로젝트의 종속성 합법성 검증 |
go mod why | 프로젝트에서 종속성을 사용하는 곳 설명 |
go clean -modcache | 프로젝트 모듈 종속성 캐시 삭제 |
go list -m | 모듈 목록 |
go mod cmd 에서 명령어에 대한 자세한 정보를 확인하십시오.
모듈 저장
Go Mod 을 사용하여 프로젝트를 관리할 때 모듈 캐시는 기본적으로 $GOPATH/pkg/mod 디렉토리에 저장되며 $GOMODCACHE 를 수정하여 다른 위치에 저장하도록 지정할 수도 있습니다.
$ go env -w GOMODCACHE=당신의 모듈 캐시 경로동일한 머신의 모든 Go Module 프로젝트는 이 디렉토리의 캐시를 공유하며 캐시에는 크기 제한이 없고 자동으로 삭제되지 않습니다. 캐시에서 압축 해제된 종속성 소스 파일은 읽기 전용이며 캐시를 비우려면 다음 명령을 실행할 수 있습니다.
$ go clean -modcache$GOMODCACHE/cache/download 디렉토리에는 종속성의 원본 파일 (해시 파일, 원본 압축 패키지 등) 이 저장됩니다. 아래 예제와 같습니다.
$ ls $(go env GOMODCACHE)/cache/download/github.com/246859/hello/@v -1
list
v1.0.0.info
v1.0.0.lock
v1.0.0.mod
v1.0.0.zip
v1.0.0.ziphash압축 해제된 종속성 조직 형태는 다음과 같으며 지정된 모듈의 소스 코드입니다.
$ ls $(go env GOMODCACHE)/github.com/246859/hello@v1.0.0 -1
LICENSE
README.md
cmd/
example/
go.mod
hello.go
hello_test.go버전 선택
Go 는 종속성 버전 선택 시 최소 버전 선택 원칙을 따릅니다. 아래는 공식 웹사이트의 예제로 주 모듈이 모듈 A 의 1.2 버전과 모듈 B 의 1.2 버전을 인용하고 모듈 A 의 1.2 버전이 모듈 C 의 1.3 버전을 인용하며 모듈 B 의 1.2 버전이 모듈 C 의 1.4 버전을 인용하고 모듈 C 의 1.3 과 1.4 버전이 모두 모듈 D 의 1.2 버전을 인용합니다. 최소 사용 가능 버전 원칙에 따라 Go 는 최종적으로 A1.2, B1.2, C1.4 및 D1.2 버전을 선택합니다. 여기서 연한 파란색은 go.mod 파일이 로드한 것이며 테두리로 표시된 것은 최종 선택된 버전입니다.
공식 웹사이트에서는 다른 몇 가지 예제 도 제공하며 대체로 비슷한 의미입니다.
go.mod
모든 Go Mod 프로젝트를 생성할 때마다 go.mod 파일이 생성되므로 go.mod 파일에 익숙해지는 것이 매우 필요하지만 대부분의 경우 go.mod 파일을 수동으로 수정할 필요는 없습니다.
module golearn
go 1.20
require github.com/gin-gonic/gin v1.9.0
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)파일에서 대부분의 종속성 주소에 github 등의 글자가 포함되어 있음을 알 수 있습니다. 이는 Go 에는 공용 종속성 저장소가 없으며 대부분의 오픈소스 프로젝트가 Github 에 호스팅되어 있기 때문입니다. 일부는 자체적으로 저장소를 구축하기도 합니다 (예: google.golang.org/protobuf, golang.org/x/crypto). 일반적으로 이 일련의 URL 은 동시에 Go 프로젝트의 모듈 이름이기도 한데 여기에는 한 가지 문제가 있습니다. URL 은 대소문자를 구분하지 않지만 종속성을 저장하는 폴더는 대소문자를 구분하므로 go get github.com/gin-gonic/gin 과 go get github.com/gin-gonic/Gin 은 동일한 종속성을 인용하지만 로컬에 저장되는 경로가 다릅니다. 이 경우 Go 는 대문자를 직접 저장 경로로 사용하지 않고 !소문자 로 이스케이프합니다 (예: github.com\BurntSushi 는 최종적으로 github.com\!burnt!sushi 로 이스케이프됨).
module
module 키워드는 현재 프로젝트의 모듈 이름을 선언하며 go.mod 파일에는 하나의 module 키워드만 나타날 수 있습니다. 예제의
module golearn는 현재 모듈 이름이 golearn 임을 나타냅니다. 예를 들어 Gin 종속성의 go.mod 파일을 열면 해당 module 이름을 확인할 수 있습니다.
module github.com/gin-gonic/ginGin 의 모듈 이름은 종속성을 다운로드할 때 사용하는 주소이며 이는 일반적으로 권장되는 모듈 이름 형식인 도메인/사용자/저장소명 입니다.
TIP
주의할 점은 주 버전이 1 보다 클 때는 주 버전 번호가 모듈 이름에 반영되어야 한다는 점입니다. 예를 들어
github.com/my/example버전이 v2.0.0 으로 업그레이드되면 모듈 이름을 다음과 같이 수정해야 합니다.
github.com/my/example/v2기존 프로젝트에서 구버전을 인용했고 새 버전에서 구분하지 않으면 종속성 인용 시 경로가 모두 일치하므로 사용자가 주 버전 변경으로 인한 호환되지 않는 변경을 구분할 수 없어 프로그램 오류가 발생할 수 있습니다.
Deprecation
module 의 위一行 주석에 Deprecated 를 표시하여 해당 모듈이 폐기되었음을 나타냅니다. 예를 들어
// Deprecated: use example.com/mod/v2 instead.
module example.com/modgo
go 키워드는 현재 프로젝트 작성에 사용된 Go 버전을 나타내며 버전 번호는 의미적 버전 규칙을 따라야 합니다. Go 버전에 따라 Go Mod 는 다른 동작을 보입니다. 아래는 간단한 예제로 Go 사용 가능 버전 번호는 공식에서 직접 확인하십시오.
go 1.20require
require 키워드는 외부 종속성을 인용했음을 나타냅니다. 예를 들어
require github.com/gin-gonic/gin v1.9.0형식은 require 모듈명 버전 이며 여러 인용이 있을 때는 괄호로 묶을 수 있습니다.
require (
github.com/bytedance/sonic v1.8.0 // indirect
)// indirect 주석이 있는 종속성은 현재 프로젝트에서 직접 인용하지 않은 것으로 프로젝트가 직접 인용한 종속성이 해당 종속성을 인용했을 수 있으므로 현재 프로젝트而言 간접 인용입니다. 앞에서 주 버전 변경 시 모듈 이름에 반영해야 한다고 언급했는데 이 규칙을 따르지 않는 모듈을 비규격 모듈이라고 하며 require 시 incompatible 주석이 추가됩니다.
require example.com/m v4.1.2+incompatible의사 버전
위의 go.mod 파일에서 일부 종속성 패키지의 버전이 의미적 버전 번호가 아닌 알 수 없는 문자열임을 알 수 있습니다. 이는 실제로 해당 버전의 CommitID 에 해당합니다. 의미적 버전은 일반적으로 특정 Release 를 가리키며 의사 버전 번호는 특정 Commit 으로 세분화할 수 있습니다. 일반 형식은 vx.y.z-yyyyMMddHHmmss-CommitId 입니다. vx.y.z 가 반드시 실제로 존재하는 것은 아니므로 의사 버전이라고 합니다. 아래 예제의 v0.0.0 은 존재하지 않으며 실제로 유효한 것은 이후 12 자리 CommitID 입니다.
// CommitID 는 일반적으로 앞 12 자리를 사용
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect마찬가지로 종속성을 다운로드할 때 의미적 버전 번호 대신 CommitID 를 지정할 수도 있습니다.
go get github.com/chenzhuoyu/base64x@fe3a3abad311exclude
exclude 키워드는 지정된 버전의 종속성을 로드하지 않음을 나타냅니다. 동시에 require 에서 동일한 버전의 종속성을 인용하더라도 무시됩니다. 이 키워드는 주 모듈에서만 유효합니다. 예를 들어
exclude golang.org/x/net v1.2.3
exclude (
golang.org/x/crypto v1.4.5
golang.org/x/text v1.6.7
)replace
replace 는 지정된 버전의 종속성을 대체합니다. 모듈 경로와 버전을 사용하거나 다른 플랫폼의 지정된 파일 경로를 사용할 수 있습니다. 예제
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
replace (
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
golang.org/x/net => example.com/fork/net v1.4.5
golang.org/x/net v1.2.3 => ./fork/net
golang.org/x/net => ./fork/net
)=> 왼쪽의 버전만 대체되며 다른 버전의 동일한 종속성은 정상적으로 접근할 수 있습니다. 로컬 경로를 사용하든 모듈 경로를 사용하든 대체하든 대체 모듈에 go.mod 파일이 있으면 해당 module 지시문이 대체된 모듈 경로와 일치해야 합니다.
retract
retract 지시문은 retract 가 지정한 버전 또는 버전 범위의 종속성을 의존하지 않아야 함을 나타냅니다. 예를 들어 새 버전 출시 후 중대한 문제를 발견한 경우 retract 지시문을 사용할 수 있습니다.
일부 버전 철회
retract (
v1.0.0 // Published accidentally.
v1.0.1 // Contains retractions only.
)버전 범위 철회
retract v1.0.0
retract [v1.0.0, v1.9.9]
retract (
v1.0.0
[v1.0.0, v1.9.9]
)go.sum
go.sum 파일은 프로젝트 생성 초기에는 존재하지 않으며 외부 종속성을 실제로 인용한 후에야 생성됩니다. go.sum 파일은 사람이 읽기에 적합하지 않으며 수동으로 수정하는 것도 권장하지 않습니다. 이 파일의 주요 역할은 일관된 빌드 문제를 해결하는 것입니다. 즉 다른 사람이 다른 환경에서 동일한 프로젝트를 빌드할 때 인용하는 종속성 패키지가 완전히 동일해야 한다는 것이며 이는 go.mod 파일만으로는 보장할 수 없습니다.
다음으로 종속성을 다운로드할 때 Go 가 처음부터 끝까지 무엇을 하는지 살펴보겠습니다. 먼저 다음 명령을 사용하여 종속성을 다운로드합니다.
go get github.com/bytedance/sonic v1.8.0go get 명령은 먼저 종속성 패키지를 로컬 캐시 디렉토리에 다운로드하며 일반적으로 이 디렉토리는 $GOMODCACHE/cache/download/ 입니다. 이 디렉토리는 도메인 이름에 따라 다른 웹사이트의 종속성 패키지를 구분하므로 다음과 같은 디렉토리 구조를 볼 수 있습니다.
$ ls
cloud.google.com/ go.opencensus.io/ gopkg.in/ nhooyr.io/
dmitri.shuralyov.com/ go.opentelemetry.io/ gorm.io/ rsc.io/
github.com/ go.uber.org/ honnef.co/ sumdb/
go.etcd.io/ golang.org/ lukechampine.com/
go.mongodb.org/ google.golang.org/ modernc.org/그렇다면 위 예제에서 다운로드한 종속성 패키지가 저장되는 경로는 다음과 같습니다.
$GOMODCACHE/cache/download/github.com/bytedance/sonic/@v/가능한 디렉토리 구조는 다음과 같으며 몇 가지 버전으로 명명된 파일이 있습니다.
$ ls
list v1.8.0.lock v1.8.0.ziphash v1.8.3.mod
v1.5.0.mod v1.8.0.mod v1.8.3.info v1.8.3.zip
v1.8.0.info v1.8.0.zip v1.8.3.lock v1.8.3.ziphash일반적으로 이 디렉토리에는 해당 종속성의 알려진 버전 번호를 기록하는 list 파일이 하나 있으며 각 버전에는 다음과 같은 파일이 있습니다.
zip: 종속성 소스 코드 압축 패키지ziphash: 종속성 압축 패키지에 따라 계산된 해시 값info: json 형식의 버전 메타데이터mod: 해당 버전의go.mod파일lock: 임시 파일 (공식에서도 용도는 명시하지 않음)
일반적으로 Go 는 압축 패키지와 go.mod 두 파일의 해시 값을 계산한 후 GOSUMDB 가 지정한 서버 (기본적으로 sum.golang.org) 에서 해당 종속성 패키지의 해시 값을 조회합니다. 로컬에서 계산된 해시 값과 조회된 결과가 일치하지 않으면 더 이상 실행되지 않습니다. 일치하면 go.mod 파일을 업데이트하고 go.sum 파일에 두 개의 레코드를 삽입합니다. 대략 다음과 같습니다.
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=TIP
GOSUMDB 를 비활성화하면 Go 는 로컬에서 계산된 해시 값을 직접 go.sum 파일에 기록합니다. 일반적으로 이렇게 하는 것은 권장하지 않습니다.
일반적으로 각 종속성에는 두 개의 레코드가 있으며 첫 번째는 압축 패키지의 해시 값이고 두 번째는 종속성 패키지의 go.mod 파일 해시 값입니다. 레코드 형식은 모듈명 버전 알고리즘 이름:해시 값입니다. 일부 구식 종속성 패키지에는 go.mod 파일이 없을 수 있으므로 두 번째 해시 레코드가 없을 수 있습니다. 이 프로젝트가 다른 사람의 환경에서 빌드될 때 Go 는 go.mod 에 지정된 로컬 종속성의 해시 값을 계산한 후 go.sum 에 기록된 해시 값과 비교합니다. 해시 값이 일치하지 않으면 종속성 버전이 다르다는 것이므로 빌드를 거부합니다. 이 경우 로컬 종속성과 go.sum 파일이 모두 수정되었을 수 있지만 go.sum 은 GOSUMDB 조회 기록을 거치므로 go.sum 파일을 더 신뢰합니다.
비공개 모듈
Go Mod 의 대부분의 도구는 오픈소스 프로젝트를 대상으로 하지만 Go 는 비공개 모듈도 지원합니다. 비공개 프로젝트의 경우 일반적으로 다음 몇 가지 환경 설정을 구성하여 모듈 비공개 처리를 수행합니다.
GOPROXY: 종속성 프록시 서버 컬렉션GOPRIVATE: 비공개 모듈의 모듈 경로 접두사 일반 패턴 목록. 모듈 이름이 규칙에 맞으면 해당 모듈은 비공개 모듈이며 구체적인 동작은 GONOPROXY 및 GONOSUMDB 와 일치합니다.GONOPROXY: 프록시에서 다운로드하지 않는 모듈 경로 접두사 일반 패턴 목록. 규칙에 맞으면 모듈 다운로드 시 GOPROXY 를 사용하지 않고 버전 관리 시스템에서 직접 다운로드를 시도합니다.GONOSUMDB: GOSUMDB 공개 검증을 수행하지 않는 모듈 경로 접두사 일반 패턴 목록. 규칙에 맞으면 모듈 다운로드 검증 시 checksum 의 공개 데이터베이스를 사용하지 않습니다.GOINSECURE: HTTP 및 기타 안전하지 않은 프로토콜을 통해 검색할 수 있는 모듈 경로 접두사 일반 패턴 목록.
워크스페이스
앞에서 go.mod 파일이 replace 지시문을 지원한다고 언급했는데 이를 통해 로컬에서 출시할 시간이 없는 수정 사항을 일시적으로 사용할 수 있습니다.
replace (
github.com/246859/hello v1.0.1 => ./hello
)컴파일 시 go 는 로컬 hello 모듈을 사용하며 이후 새 버전을 출시한 후 제거합니다.
하지만 replace 지시문을 사용하면 go.mod 파일 내용이 수정되며 이 수정 사항이 실수로 원격 저장소에 커밋될 수 있습니다. 이는 우리가 원하지 않는 상황입니다. replace 지시문이 지정한 target 이 네트워크 URL 이 아닌 파일 경로이기 때문에 이 머신에서 사용 가능한 경로가 다른 머신에서는 사용 불가능할 수 있으며 파일 경로는 크로스 플랫폼 측면에서도 큰 문제입니다. 이러한 문제를 해결하기 위해 워크스페이스가 등장했습니다.
워크스페이스 (workspace) 는 Go 1.18 에서 도입된 멀티 모듈 관리에 대한 새로운 솔루션으로 로컬 멀티 모듈 개발 작업을 더 잘 수행하는 것을 목표로 합니다. 아래에서는 예제를 통해 설명하겠습니다.
예제 저장소: 246859/work: go work example (github.com)
예제
먼저 프로젝트에는 auth, user 두 개의 독립적인 Go 모듈이 있습니다.
$ ls -1
LICENSE
README.md
auth
go.work
userauth 모듈은 user 모듈의 구조체 User 에 의존하며 내용은 다음과 같습니다.
package auth
import (
"errors"
"github.com/246859/work/user"
)
// Verify user credentials if is ok
func Verify(user user.User) (bool, error) {
password, err := query(user.Name)
if err != nil {
return false, err
}
if password != user.Password {
return false, errors.New("authentication failed")
}
return true, nil
}
func query(username string) (string, error) {
if username == "jack" {
return "jack123456", nil
}
return "", errors.New("user not found")
}user 모듈 내용은 다음과 같습니다.
package user
type User struct {
Name string
Password string
Age int
}이 프로젝트에서 우리는 다음과 같이 go.work 파일을 작성할 수 있습니다.
go 1.22
use (
./auth
./user
)내용은 매우 이해하기 쉽습니다. use 지시문을 사용하여 어떤 모듈이 컴파일에 참여하는지 지정합니다. 다음으로 auth 모듈의 코드를 실행합니다.
// auth/example/main.go
package main
import (
"fmt"
"github.com/246859/work/auth"
"github.com/246859/work/user"
)
func main() {
ok, err := auth.Verify(user.User{Name: "jack", Password: "jack123456"})
if err != nil {
panic(err)
}
fmt.Printf("%v", ok)
}다음 명령을 실행하여 결과를 통해 모듈이 성공적으로 가져왔음을 알 수 있습니다.
$ go run ./auth/example
true이전 버전에서는 이 두 개의 독립적인 모듈에 대해 auth 모듈이 user 모듈의 코드를 사용하려면 두 가지 방법뿐이었습니다.
- user 모듈의 수정 사항을 커밋하여 원격 저장소에 푸시하고 새 버전을 출시한 후
go.mod파일을 지정된 버전으로 수정 go.mod파일을 수정하여 종속성을 로컬 파일로 리디렉션
두 방법 모두 go.mod 파일을 수정해야 하지만 워크스페이스의 존재는 go.mod 파일을 수정하지 않고도 다른 모듈을 가져올 수 있게 합니다. 하지만 이해해야 할 점은 go.work 파일은 개발 과정에서만 사용되며 존재 이유는 의존성 관리가 아니라 로컬 개발을 더 편리하게 수행하기 위한 것이라는 점입니다. 이는 단순히 출시까지의 과정을 일시적으로 건너뛰게 하여 user 모듈의 새 수정 사항을 기다리지 않고 바로 사용할 수 있게 해줍니다. user 모듈 테스트가 완료되면 결국 새 버전을 출시해야 하며 auth 모듈도 결국 go.mod 파일을 수정하여 최신 버전을 인용해야 합니다 (이 과정은 go work sync 명령으로 완료할 수 있음). 따라서 일반적인 Go 개발 과정에서 go.work 는 VCS 에 커밋되어서는 안 됩니다 (예제 저장소의 go.work 는 데모용일 뿐). 내용은 로컬 파일에 의존하며 기능도 로컬 개발로만 제한되기 때문입니다.
명령어
다음은 워크스페이스의 몇 가지 명령어입니다.
| 명령어 | 소개 |
|---|---|
| edit | go.work 편집 |
| init | 새 워크스페이스 초기화 |
| sync | 워크스페이스의 모듈 종속성 동기화 |
| use | go.work 에 새 모듈 추가 |
| vendor | 종속성을 vendor 형식으로 복사 |
go work cmd 에서 명령어에 대한 자세한 정보를 확인하십시오.
지시문
go.work 파일의 내용은 매우 간단하며 세 가지 지시문만 있습니다.
go: Go 버전 지정use: 사용할 모듈 지정replace: 대체할 모듈 지정
use 지시문을 제외하고 다른 두 가지는 기본적으로 go.mod 의 지시문과 동일하지만 go.work 의 replace 지시문은 모든 모듈에 작용합니다. 완전한 go.work 는 다음과 같습니다.
go 1.22
use(
./auth
./user
)
repalce github.com/246859/hello v1.0.0 => /home/jack/code/hello