본문 바로가기

Programming Language/Go

[go mod 번역] Go에서 Go Modules 사용해보기

반응형

이번 포스팅은 Go 블로그에 업데이트되어있는

Go modules을 사용하는 방법에 관한 포스팅입니다.

 

 

 

 

 

번역 포스팅의 원본 링크 주소

blog.golang.org/using-go-modules

 

Using Go Modules - The Go Blog

Tyler Bui-Palsulich and Eno Compton 19 March 2019 Introduction This post is part 1 in a series. Go 1.11 and 1.12 include preliminary support for modules, Go’s new dependency management system that makes dependency version information explicit and easier

blog.golang.org

 

새로운 module 생성하기

hello.go 파일 생성

일단 $GOPATH/src의 바깥쪽에서 빈 디렉터리를 하나 생성한다. 그리고 아래와 같이 hello.go 이름으로 소스파일을 생성한다.

package hello

func Hello() string {
    return "Hello, world"
}

 

hello_test.go 파일 생성

그리고 이 hello.go를 테스트하기 위해 테스트 파일인 hello_test.go를 작성한다.

package hello

import (
	"fmt"
	"testing"
)

func TestHello(t *testing.T) {
	want := "Hello, world"
	fmt.Println(want)
	if got := Hello(); got != want {
		t.Errorf("Hello() = %q, want %q", got, want)
	}
}

 

go test 테스트 진행

$ go test

파일 작성을 완료한 뒤, go test를 실행하면 아래와 같은 화면을 확인할 수 있다.

 

go test 실행 결과

> 위 이미지의 출력내용에서 마지막 줄은 전반적인 패키지 테스트를 요약한 것.

> 현재 $GOPATH 바깥쪽에서 작업을 진행하고 있다.

> 따라서 go 커맨드는 현재 디렉터리에 대한 import 정보를 알지 못한다.

> 그로 인해 잘못된 경로를 생성하게 된다.

 

go mod init

현재 디렉터리는 모듈이 아닌 패키지를 포함하고 있다. 그 이유는 아직 go.mod 파일이 존재하지 않기 때문이다.

 

go mod init 명령어를 실행하여, 현재 디렉터리를 모듈의 루트로 만들고, go test를 실행한다.

$ go mod init github.com/lecture

> 위 명령어 실행 시, 현재 폴더에 go.mod와 go.sum 파일이 생성된다.

 

go mod init 후,  go test 진행

go mod 와 go test 실행결과

> 이렇게 실행하면, 첫 번째 모듈을 작성을 하고 테스트를 실행한 것이 된다.

> 위 이미지를 확인하면, 마지막 줄의 경로가 바뀐 것을 확인할 수 있다.

 

go.mod 파일 확인

$ cat go.mod

> go mod init 명령어를 통해 생성된 go.mod 파일을 확인

> go.mod 파일은 오직 모듈의 루트 안에 존재하게 된다.

> 서브 디렉터리에 있는 패키지는 현재 존재하는 모듈의 경로에 하위 디렉터리 경로를 더하여 임포트 한다.

> 예를 들어, 현재 폴더에 서브 디렉터리인 world를 만든다면, 우리는 go mod init을 또 실행할 필요가 없다.

> 패키지는 자동적으로 github.com/lecture의 한 부분임을 인지하고, import 경로를 github.com/lecture/world로 생성하게 된다.

 

의존성 추가하기

go modules이 생성된 주요한 동기는, 다른 개발자들에 의해 작성된 코드를 편하게 사용하기 위함이다.

 

hello.go 파일 수정

현재 hello.go 파일에서 새로 패키지를 호출하게 하기 위해, 아래와 같이 코드를 수정한다.

package hello

import "rsc.io/quote"

func Hello() string {
    return quote.Hello()
}

 

go test 실행

$ go test

위와 같이 파일을 수정한 뒤, go test를 실행하면 아래와 같은 화면을 확인할 수 있다.

 

위 이미지와 같이 필요한 패키지를 go module에서 다운로드 받은 뒤 사용하게 된다.

> go.mod에 리스트 된 특별한 모듈 의존성을 사용하여 import 문제를 해결한다.

> go.mod안에 제공되지 않은 패키지 import에 직면하면 go 커맨드는 패키지를 자동으로 찾아서 최신 버전으로 go.mod에 추가한다.

 

> go test 실행 후 출력을 확인하면, 현재 새로운 패키지인 rsc.io/quote를 rsc.io/quote v1.5.2 모듈을 사용하여 해결한다. 

> 또한 rsc.io/quote 패키지에서 사용하는 두 가지 종속성 패키지인 rsc.io/sampler와 golang.org/x/text를 다운로드한다.

 

> 그러나 최종적으로 go.mod 파일에는 직접적인 종속성이 있는 패키지만 기록된다.

 

go.mod 파일 확인

go.mod

$ cat go.mod

> go.mod 파일을 다시 확인해보면, 아래와 같이 사용된 패키지가 하나 추가되어있다.

 

 

>다시 go test를 실행하게 되었을 경우, go는 위에서 했던 작업을 반복하지 않는다. 

> go.mod 파일이 최신 상태이며, 다운로드된 모듈은 로컬에 캐싱이 되어있기 때문이다. ($GOPATH/pkg/mod)

 

> 위에서 보았듯이, 하나의 의존성을 추가하게 되면, 다른 간접 의존성도 함께 다운로드하게 된다. 

 

go list

$ go list -m list

> 해당 커맨드를 입력하면 의존성이 있는 모든 리스트를 가져온다.

> go list의 출력은, 현재 모듈 즉 메인 모듈이 항상 첫 줄에 존재한다. 그리고 모듈에 경로에 따라 의존성이 정렬되어 출력된다.

 

 

go.sum

$ cat go.sum

> go 커맨드는 go.sum 파일을 사용하여 이러한 모듈의 향후 다운로드가 첫 번째 다운로드와 동일한 비트를 검색하도록 하여 프로젝트가 의존하는 모듈이 악의적, 우발적 또는 기타 이유로 예기치 않게 변경되지 않도록 한다.

 

 

의존성 업그레이드

Go 모듈을 사용하면 버전이 시맨틱 버전 태그로 참조된다. 시맨틱 버전은 major, minor, patch 이렇게 총 3 부분으로 구성된다.

예를 들어 v0.1.2는 major 버전은 0, minor 버전은 1, patch 버전은 2이다.

 

위에서와 같이 go-list -m all의 출력을 보면, 태그 되지 않은 golang.org/x/text를 사용하는 것을 확인할 수 있다.

 

이 패키지를 최신 버전으로 업그레이드 한 뒤, go test가 정상 작동하는지 확인한다.

 

패키지 업그레이드 예제 1

go get 패키지

$ go get golang.org/x/text

> 최신 버전으로 업그레이드하기 위해 go get을 사용하여 패키지를 다운로드한다.

 

go test 진행

$ go test

> 패키지 업그레이드 이후 정상 작동하는지 확인을 위한 go test 진행

 

go list 

$ go list -m all

> 의존성 패키지의 버전이 업그레이드되어있는지 확인

 

go.mod 파일 확인

$ cat go.mod

> 의존성 패키지의 버전이 업그레이드되어있는지 확인

 

실제 명령어에 대한 출력

실제 명령어의 output 화면

 

> 테스트는 정상적으로 작동하며, golang.org/x/text 도 업그레이드가 되었다.

> 또한 go.mod 파일도 새롭게 작성이 되었다.

(여기서 indirect 표시는, 직접적으로 이 패키지를 사용하지 않는다는 표시이다)

 

 

패키지 업그레이드 예제 1

이번에는 rsc.io/quote 패키지를 업그레이드한다.

 

go get 패키지

$ go get rsc.io/sampler

 

go test 

$ go test

 

실행 결과 확인

> 하지만 이번 테스트는 실패하는 것을 확인할 수 있다.

> 그 이유는 최신 버전의 rsc.io/sampler 가 현재의 사용과 호환되지 않음을 보여준다.

 

go list 

$ go list -m -versions rsc.io/sampler

$ go get rsc.io/sampler@v1.3.1

> 해당 모듈의 사용 가능한 태그 버전을 확인한다.

> 출력되어 나온 버전 중 하나를 택하여 다시 다운로드한다.

> 사용 가능한 버전으로 변경한 이후에, 다시 go test가 정상적으로 작동되는 것을 확인할 수 있다.

 

 

새로운 major 버전으로 의존성 추가하기

hello.go에 함수 추가

package hello

import (
    "rsc.io/quote"
    quoteV3 "rsc.io/quote/v3"
)

func Hello() string {
    return quote.Hello()
}

func Proverb() string {
    return quoteV3.Concurrency()
}

> hello.go파일에 Proverb()를 추가한다.

 

hello_test.go에 테스트 함수 추가

# 기존 test함수에 아래 함수를 추가한다.

func TestProverb(t *testing.T) {
    want := "Concurrency is not parallelism."
    if got := Proverb(); got != want {
        t.Errorf("Proverb() = %q, want %q", got, want)
    }
}

> hello.go파일에 추가한 Proverb() 함수를 테스트할 수 있는 TestProverb()를 추가한다.

 

go test 실행

 

rsc.io/quote/v3라는 새로운 버전을 다운 받으면서 테스트는 정상 실행이 된다.

 

의존성 확인

여기서 주목해야 하는 점은 우리의 모듈이 이제 rsc.io/quote와 rsc.io/quote/v3 2개의 의존성을 가지는 점이다.

go list를 사용하여 현재 의존성 리스트를 확인 할 수 있다.

$ go list -m all

$ go list -m rsc.io/q...

> 명령어 실행 결과 확인

 

시맨틱 임포트 버저닝

고모듈의 각각의 다른 메이저 버전(v1, v2..)들은 다른 모듈 경로를 사용한다. v2 버전일 경우, 경로는 반드시 메이저 버전으로 끝나야 한다.

예를 들어, rsc.io/quote의 v3는 rsc.io/quote와 같지 않다. 각 모듈은 다른 경로를 사용한다.

 

규칙을 시맨틱 임포트 버저닝이라고 하며 호환되지 않는 패키지 (주요 버전이 다른 패키지)에 다른 이름을 부여합니다.

 

반대로 rsc.io/quote의 v1.6.0은 v1.5.2와 역 호환되어야 하므로 rsc.io/quote를 재사용한다.

 

go 명령을 사용하면 빌드에 특정 모듈 경로의 버전을 최대 하나만 포함할 수 있습니다. 즉, 각 주요 버전 중 최대 하나를 의미합니다. 이것으로 모듈 작성자에게 단일 모듈 경로의 중복 가능성에 대한 명확한 규칙을 제시한다. 따라서 프로그램은 rsc.io/quote의 v1.6.0와 v1.5.2를 각각 함께 빌드할 수 없다.

 

새로운 major 버전으로 의존성 업그레이드

이제 rsc.io/quote 버전을 rsc.io/quote/v3로 업그레이드를 시켜 v3 하나의 버전만 사용하게 할 것이다.

 

주요한 버전의 변화로 인해, 몇몇 API는 삭제되거나, 이름이 변경되거나, 호환되지 않는 길로 변경될지도 모른다. 따라서 문서를 읽고 변경된 점을 확인하고 코드에 반영해야 한다.

 

go doc

$ go doc rsc.io/quote/v3

> 특정 패키지에 대한 go 문서 확인

> 출력을 통해 기존 Hello() 함수가 HelloV3()로 바뀐 것을 확인할 수 있다.

 

> 따라서 버전 업그레이드를 위해 실제 코드에서 사용된 API의 이름을 변경해줘야 한다.

 

hello.go 코드 변경

package hello

import "rsc.io/quote/v3"

func Hello() string {
    return quote.HelloV3()
}

func Proverb() string {
    return quote.Concurrency()
}

> 이때는 "rsc.io/quote/v3" 하나만 사용하기 때문에 더 이상 alias를 선언하지 않아도 된다.

 

go list / go mod

사용하지 않은 의존성 제거를 위해 현재 사용하는 의존성 리스트를 확인한다.

$ go list -m all

$ cat go.mod

> 위에서 우리는 사용한 rsc.io/quote를 지웠다. 그러나 여전히 go list와 go.mod 파일에는 존재한다.

 

go list, go mod 확인

 

> go build 또는 go test를 사용하여 패키지를 빌드하면, 누락된 항목을 추가하는 것은 쉽게 알 수 있지만, 안전하게 제거하는 것은 어렵다.

 

> 종속성 제거는 모듈의 모든 패키지와 해당 패키지에 대해 가능한 모든 빌드 태그 조합을 확인한 후에만 수행할 수 있다. 일반 빌드 명령은 이 정보를 로드하지 않으므로 종속성을 안전하게 제거할 수 없다.

 

go mod tidy

$ go mod tidy

> go mod tidy 명령어는 사용하지 않는 의존성을 제거해준다.

 

요약정리

Go 모듈은 고의 의존성 관리 툴이다. 현재 모듈 기능은 모든 고 버전을 지원한다.(Go 1.11와 Go 1.12)

  • go mod init : 새로운 모듈 생성, go.mod 파일을 초기화
  • go build, go test : 다른 패키지 빌딩 커맨드로써, 필요한 의존성을 go.mod에 추가한다.
  • go list -m all : 현재 모듈의 의존성을 모두 출력
  • go get : 특정 의존성의 필요한 버전으로 변경, 혹은 새로운 의존성 추가
  • go mod tidy : 사용하지 않는 의존성 삭제
반응형