Golang 기초 (12) : 패키지 & 모듈 & 워크스페이스에 대하여
안녕하세요, 주니어 개발자 Eon입니다.
이번 포스트는 패키지 & 모듈 & 워크스페이스에 관한 내용입니다.
📝 Package
Golang의 코드를 묶는 단위이며, 모든 코드는 패키지에 속해야 합니다.
📌 패키지를 선언하는 방법
📍 package main
package main func main(){...}
main 패키지
를 선언할 수 있고,main()
함수를 작성할 수 있습니다.
main 패키지
가 가지는 의미는 다음과 같습니다.
'프로그램의 시작 패키지'
모든 Golang 프로그램은 main 패키지의 main()함수부터 실행됩니다.
(아래에서 다루지만 패키지 초기화 함수와는 다릅니다.)
📍 package other
package other func mainIsNotAllowed(){...}
main 패키지
외에는main()
함수를 가질 수 없습니다.
📌 패키지를 사용하는 방법
package main import "fmt" func main() { fmt.Println("Hello World!!") } // Hello World!!
import
를 하고 나면 그 패키지가 제공하는 것들(함수, 구조체 등)을 사용할 수 있습니다.
package other import "some-other-package" func OtherFunc() { someotherpackage.SOPfunc() }
'-'를 포함한
import
를 하면, 패키지를 사용할 때 '-'를 빼고 사용할 수 있습니다.
package other import "some_other_package" func OtherFunc() { some_other_package.SOPfunc() }
'_'를 포함한
import
는 변형없이 사용할 수 있습니다.
package other import ( "github.com/just/awsome" va "github.com/very/awsome" na "github.com/not/awsome" ) func OtherFunc() { awsome.JustAwsomeFunc() va.VeryAwsomeFunc() na.NotAwsomeFunc() }
import
한 패키지명이 길고 복잡하거나 겹치는 경우, 위와 같이 alias를 줄 수 있습니다.
package other import _ "github.com/just/initialize"
특정 패키지의 초기화가 필요한 경우, 위와 같이 직접적으로 사용하지 않아도 특정 패키지를 포함할 수 있습니다.
📍 같은 패키지, 다른 .go 파일
같은 패키지 내의 모든 소스코드는 안의 자원을 공유합니다.
${GOSRC}/example/
⊢ ex1.go
∣ ⊢ UpperSth()
∣ ⨽ lowerSth()
⨽ ex2.go
⊢ ExportSth()
⨽ cannotExportSth()
package example
두 파일 모두 같은 패키지 내에 있습니다.
이 때, ex1.go
의 아무 함수 내에 ex2.go
의 모든 함수를 호출할 수 있습니다.
(파일이 분리되어 있어도 패키지가 같으면 바로 사용이 가능)
단, 패키지 외부에서 함수를 호출할 때는 함수 이름이 대문자로 시작하는 경우만 호출할 수 있습니다.
https://velog.io/@vamos_eon/Golang-function#-the-rule-of-naming-in-golang
📍 패키지 초기화 함수
func init(){...}
init()
함수는 패키지가import
될 때, 호출없이 가장 먼저 실행되는 함수입니다.
패키지 내에서 중복해서 선언도 가능하며, 사용자 임의로 호출은 불가합니다.
프로그램 실행 시, main 함수보다도 먼저 호출됩니다.
init() 함수 실행 순서
- 가장 안쪽 패키지부터
- 파일 정렬 위에서 아래로
- 함수 정렬 위에서 아래로
📝 Module
모듈은 종속성 관리를 위해 Golang이 지원하는 것입니다.
모듈은 패키지의 모음이며, 모듈 파일이 있는 곳이 패키지의 루트 경로가 됩니다.
(모듈 : 패키지 = 1 : N)
📌 Module의 초기화
go mod init module_path # 1 go mod init module # 2 go mod init module/test # 3
모듈은 위와 같이 초기화할 수 있습니다.
2번의 경우, 빌드하면 실행파일 이름이module
이 되고, root 패키지의 경로는 module/ 입니다.
3번의 경우, 빌드하면 실행파일 이름이test
가 되고, root 패키지의 경로는 module/test/ 입니다.
📌 Module의 구성
module module/test go 1.17 require ( github.com/package/example v1.2.0 ... )
go.mod
파일을 보면 모듈 이름과 Golang 버전이 표기됩니다.
그리고 외부 패키지를 포함했다면, 외부 패키지의 버전도 함께 표시합니다.
indirect
가 붙으면 직접적으로 사용하지 않더라도 사용한 외부 패키지에import
돼 있는 의존성 패키지입니다.
Golang wiki 페이지에 정리된 내용이 있습니다.
https://github.com/golang/go/wiki/Modules
https://github.com/golang/go/wiki/Modules#can-a-module-consume-a-package-that-has-not-opted-in-to-modules
📍 go.sum ?
위에서 go.mod
에 대한 내용을 다뤘습니다.
외부 패키지를 포함하여 빌드를 하고 나면 go.sum
파일이 생성됩니다.
go.sum
은 외부 패키지의 버전에 대한 checksum
을 모아둔 파일입니다.
go.sum
파일이 없으면 빌드할 때 다시 생성합니다.
📝 Workspace
Golang 코드가 짜여지고 관리되는 공간입니다.
권장 방법은 링크를 참고하시면 됩니다.
https://go.dev/doc/gopath_code#Workspaces
문서와는 조금 다르게, 조금 더 간단하게 사용할 수도 있습니다.
/home/user/
아래에 workspace
로 사용할 디렉터리를 만들고 사용할 수 있습니다.
/home/user/awsome-project/
(단일 프로그램 또는 프로젝트)
/home/user/go-practice/
(여러 프로그램 또는 프로젝트)
위와 같이 여러 workspace
를 만들 수도 있습니다.
📌 Workspace 구조
~/awsome-project/ ⊢ go.mod ⊢ go.sum ⊢ main.go ⊢ package_dir ∣ ⊢ hello.go ∣ ⨽ bye.go ⨽ other_package_dir ⊢ other_code1.go ⨽ other_code2.go
위와 같이 하나의 프로그램을 위한
workspace
를 만들고 관리할 수 있습니다.
~/go-practice/ ⊢ awsome-project/ ∣ ⊢ go.mod ∣ ⊢ go.sum ∣ ⊢ main.go ∣ ⊢ package_dir ∣ ∣ ⊢ hello.go ∣ ∣ ⨽ bye.go ∣ ⨽ external_pkg_used_dir ∣ ⊢ ext_used1.go ∣ ⨽ ext_used2.go ⊢ hello-bye ∣ ⊢ hello-bye* ∣ ⊢ go.mod ∣ ⊢ main.go ∣ ⨽ hello-bye ∣ ⊢ hello.go ∣ ⨽ bye.go ⨽ calculator ⊢ calculator* ⊢ go.mod ⊢ main.go ⨽ arithmetic-operation ⊢ sum.go ⊢ sub.go ⊢ mul.go ⊢ div.go ⨽ mod.go
위와 같이 여러 개의 프로그램을 위한
workspace
를 만들고 관리할 수 있습니다.
📌 Visual Studio Code Golang workspace
Vscode
를 사용하시는 분이라면 연습용 디렉터리 내에 여러 프로그램을 작성하고 테스트하고 싶을 겁니다.
하지만 Vscode
의 Explorer Folder 내에 같은 이름의 모듈이 존재하면 빨갛게 표시됩니다.
이는 테스트할 때마다 불편하게 합니다. (빌드도 정상적으로 되는데 괜히 마음 불편한..)
go mod init main
명령어로 간단하게 모듈 이름 상관없이 짓고 빌드하고 싶을 때 말입니다.
go.work
를 작성하면 이런 불편이 사라집니다.
📍 go.work 작성
~/go-practice/go.work
go 1.17 directory ( ./awsome-project ./hello-bye ./calculator )
go.work
file을 생성하기만 해도 모듈 이름 중복에 대한 문제는 해결됩니다.
다만 workspace를 지정해서 사용하려면 위와 같이 디렉터리 별로 설정할 수도 있습니다.
vscode go workspace 관련 이슈
: https://github.com/golang/go/issues/45713
📝 직접 만들고 사용해보자
1. main 패키지 작성
📍 workspace 세팅 및 main.go 작성
${GOSRC}/example/
⨽ main.go
package main
func main(){}
2. 모듈 초기화
📍 모듈명 설정
~/${GOSRC}/example$ go mod init local_package
3. 로컬 패키지 생성
📍 패키지 파일 생성 및 작성
${GOSRC}/example/
⊢ go.mod
⊢ main.go
⨽ something
⊢ ex1.go
⨽ ex2.go
ex1.go
package something
import "fmt"
type CaseStruct struct {
UpperValue string
lowerValue string
}
func UpperSth(){
fmt.Println("UpperSth() Called")
}
func lowerSth(){
fmt.Println("lowerSth() Called")
}
ex2.go
package something
import "fmt"
func ExportSth(){
fmt.Println("ExportSth() Called")
}
func cannotExportSth(){
fmt.Println("cannotExportSth() Called")
}
4. 로컬 패키지 사용
📍 main.go 추가 작성
package main
import "local_package/something"
func main() {
// added
sth_case := something.CaseStruct{}
sth_case.UpperValue = "Upper value."
fmt.Println("sth_case's UpperValue:", sth_case.UpperValue)
something.UpperSth()
}
📍 빌드 하기
~/${GOSRC}/example$ go build
~/${GOSRC}/example$ ls -alF
# output
total 1748
drwxrwxr-x 3 eon eon 4096 Mar 16 12:04 ./
drwxrwxr-x 4 eon eon 4096 Mar 16 11:57 ../
-rw-rw-r-- 1 eon eon 30 Mar 16 12:01 go.mod
-rwxrwxr-x 1 eon eon 1766536 Mar 16 12:04 local_package*
-rw-rw-r-- 1 eon eon 86 Mar 16 12:05 main.go
drwxrwxr-x 2 eon eon 4096 Mar 16 12:05 something/
실행 파일 이름 지정하여 빌드
~/${GOSRC}/example$ go build -o bin-name
📍 실행하기
~/${GOSRC}/example$ ./local_package
# output
sth_case's UpperValue: Upper value.
UpperSth() Called
5. 동일 패키지 함수 사용
📍 ex1.go에서 ex2.go 함수 호출하기
ex1.go
package something
import "fmt"
func UpperSth() {
fmt.Println("UpperSth() Called")
lowerSth()
ExportSth()
cannotExportSth()
}
func lowerSth() {
fmt.Println("lowerSth() Called")
}
빌드 후 실행 결과
UpperSth() Called
lowerSth() Called
ExportSth() Called
cannotExportSth() Called
6. 외부 패키지 사용
📍 외부 패키지 import 및 사용
ex2.go
package something
import (
"fmt"
"github.com/google/uuid"
)
func ExportSth() {
fmt.Println("ExportSth() Called")
}
func cannotExportSth() {
fmt.Println("cannotExportSth() Called")
}
// added
func ExternalPackage() {
fmt.Println(uuid.New())
}
main.go
package main
import "local_package/something"
func main() {
// function call edited
something.ExternalPackage()
}
📍 외부 패키지 다운로드 및 초기화
(1) 모듈 정리(다운로드, 종속관계 정리) 후 빌드
go mod tidy
go build
(2) 다운로드만 하고 빌드
go get github.com/google/uuid
go build
${GOSRC}/example/
⊢ go.mod
⊢ go.sum
⊢ main.go
⨽ something
⊢ ex1.go
⨽ ex2.go
go.sum
- 위의 두 경우 (1), (2) 모두
go.sum
파일이 생성되며 checksum
을 기록합니다.
-
모듈 정리를 안할 경우
-
go.mod
module local_package
go 1.17
require github.com/google/uuid v1.3.0 // indirect
직접 import
하는 외부 패키지임에도 불구하고 위와 같이 indirect
문구가 붙으며 사용되지 않은, 간접 dependency로써 존재하게 됩니다.
따라서 모듈을 정리해주는 과정이 필요합니다.
외부 패키지 종속성이 변경되었다면 빌드 전에 go mod tidy
명령어를 꼭 실행하는 것을 권장합니다.
go mod tidy
명령어 실행 후에는 // indirect
문구는 사라집니다.
(3) 빌드 후 실행 결과
c1c36290-2516-4e2a-ad60-696e90ec6328
7. 패키지 초기화 함수 init() 사용
📍 init() 동작 순서 테스트
main.go
package main
import "local_package/something"
func main() {
something.ExternalPackage()
}
// function added
func init(){
fmt.Println("init called in main package")
}
ex1.go
package something
import "fmt"
func init() {
fmt.Println("ex1.go init 1")
}
func UpperSth() {
fmt.Println("UpperSth() Called")
lowerSth()
ExportSth()
cannotExportSth()
}
func lowerSth() {
fmt.Println("lowerSth() Called")
}
func init() {
fmt.Println("ex1.go init 2")
}
ex2.go
package something
import (
"fmt"
"github.com/google/uuid"
)
func ExportSth() {
fmt.Println("ExportSth() Called")
}
func cannotExportSth() {
fmt.Println("cannotExportSth() Called")
}
func init() {
fmt.Println("ex2.go init 1")
}
func init() {
fmt.Println("ex2.go init 2")
}
func ExternalPackage() {
fmt.Println(uuid.New())
}
빌드 후 실행 결과
ex1.go init
ex1.go init2
ex2.go init
ex2.go init2
init called in main package
f4887be3-d7db-4b3e-9760-51a1b676cf96
📍 init() 내에서 전역변수 설정
main.go
package main
import (
"fmt"
"local_package/something"
)
func main() {
fmt.Println("final Cnt :", something.Cnt)
}
func init() {
something.Cnt = 1
}
ex1.go
package something
import (
"fmt"
)
func init() {
fmt.Println("ex1.go init 1")
Cnt = 10
}
func UpperSth() {
fmt.Println("UpperSth() Called")
lowerSth()
ExportSth()
cannotExportSth()
}
func lowerSth() {
fmt.Println("lowerSth() Called")
}
ex2.go
package something
import (
"fmt"
)
var Cnt int8
func ExportSth() {
fmt.Println("ExportSth() Called")
}
func cannotExportSth() {
fmt.Println("cannotExportSth() Called")
}
func init() {
fmt.Println("ex2.go init 1")
Cnt = 20
}
빌드 후 실행 결과
ex1.go init 1
ex2.go init 1
final Cnt : 1
${GOSRC}/example/
⨽ main.go
package main
func main(){}
~/${GOSRC}/example$ go mod init local_package
${GOSRC}/example/
⊢ go.mod
⊢ main.go
⨽ something
⊢ ex1.go
⨽ ex2.go
ex1.go
package something
import "fmt"
type CaseStruct struct {
UpperValue string
lowerValue string
}
func UpperSth(){
fmt.Println("UpperSth() Called")
}
func lowerSth(){
fmt.Println("lowerSth() Called")
}
ex2.go
package something
import "fmt"
func ExportSth(){
fmt.Println("ExportSth() Called")
}
func cannotExportSth(){
fmt.Println("cannotExportSth() Called")
}
package main
import "local_package/something"
func main() {
// added
sth_case := something.CaseStruct{}
sth_case.UpperValue = "Upper value."
fmt.Println("sth_case's UpperValue:", sth_case.UpperValue)
something.UpperSth()
}
~/${GOSRC}/example$ go build
~/${GOSRC}/example$ ls -alF
# output
total 1748
drwxrwxr-x 3 eon eon 4096 Mar 16 12:04 ./
drwxrwxr-x 4 eon eon 4096 Mar 16 11:57 ../
-rw-rw-r-- 1 eon eon 30 Mar 16 12:01 go.mod
-rwxrwxr-x 1 eon eon 1766536 Mar 16 12:04 local_package*
-rw-rw-r-- 1 eon eon 86 Mar 16 12:05 main.go
drwxrwxr-x 2 eon eon 4096 Mar 16 12:05 something/
~/${GOSRC}/example$ go build -o bin-name
~/${GOSRC}/example$ ./local_package
# output
sth_case's UpperValue: Upper value.
UpperSth() Called
ex1.go
package something
import "fmt"
func UpperSth() {
fmt.Println("UpperSth() Called")
lowerSth()
ExportSth()
cannotExportSth()
}
func lowerSth() {
fmt.Println("lowerSth() Called")
}
UpperSth() Called
lowerSth() Called
ExportSth() Called
cannotExportSth() Called
ex2.go
package something
import (
"fmt"
"github.com/google/uuid"
)
func ExportSth() {
fmt.Println("ExportSth() Called")
}
func cannotExportSth() {
fmt.Println("cannotExportSth() Called")
}
// added
func ExternalPackage() {
fmt.Println(uuid.New())
}
main.go
package main
import "local_package/something"
func main() {
// function call edited
something.ExternalPackage()
}
go mod tidy
go build
go get github.com/google/uuid
go build
${GOSRC}/example/
⊢ go.mod
⊢ go.sum
⊢ main.go
⨽ something
⊢ ex1.go
⨽ ex2.go
go.sum
- 위의 두 경우 (1), (2) 모두
go.sum
파일이 생성되며checksum
을 기록합니다.
모듈 정리를 안할 경우
-
go.mod
module local_package go 1.17 require github.com/google/uuid v1.3.0 // indirect
직접
import
하는 외부 패키지임에도 불구하고 위와 같이indirect
문구가 붙으며 사용되지 않은, 간접 dependency로써 존재하게 됩니다.
따라서 모듈을 정리해주는 과정이 필요합니다.
외부 패키지 종속성이 변경되었다면 빌드 전에go mod tidy
명령어를 꼭 실행하는 것을 권장합니다.
go mod tidy
명령어 실행 후에는// indirect
문구는 사라집니다.
c1c36290-2516-4e2a-ad60-696e90ec6328
main.go
package main
import "local_package/something"
func main() {
something.ExternalPackage()
}
// function added
func init(){
fmt.Println("init called in main package")
}
ex1.go
package something
import "fmt"
func init() {
fmt.Println("ex1.go init 1")
}
func UpperSth() {
fmt.Println("UpperSth() Called")
lowerSth()
ExportSth()
cannotExportSth()
}
func lowerSth() {
fmt.Println("lowerSth() Called")
}
func init() {
fmt.Println("ex1.go init 2")
}
ex2.go
package something
import (
"fmt"
"github.com/google/uuid"
)
func ExportSth() {
fmt.Println("ExportSth() Called")
}
func cannotExportSth() {
fmt.Println("cannotExportSth() Called")
}
func init() {
fmt.Println("ex2.go init 1")
}
func init() {
fmt.Println("ex2.go init 2")
}
func ExternalPackage() {
fmt.Println(uuid.New())
}
ex1.go init
ex1.go init2
ex2.go init
ex2.go init2
init called in main package
f4887be3-d7db-4b3e-9760-51a1b676cf96
main.go
package main
import (
"fmt"
"local_package/something"
)
func main() {
fmt.Println("final Cnt :", something.Cnt)
}
func init() {
something.Cnt = 1
}
ex1.go
package something
import (
"fmt"
)
func init() {
fmt.Println("ex1.go init 1")
Cnt = 10
}
func UpperSth() {
fmt.Println("UpperSth() Called")
lowerSth()
ExportSth()
cannotExportSth()
}
func lowerSth() {
fmt.Println("lowerSth() Called")
}
ex2.go
package something
import (
"fmt"
)
var Cnt int8
func ExportSth() {
fmt.Println("ExportSth() Called")
}
func cannotExportSth() {
fmt.Println("cannotExportSth() Called")
}
func init() {
fmt.Println("ex2.go init 1")
Cnt = 20
}
ex1.go init 1
ex2.go init 1
final Cnt : 1
이상 패키지 / 모듈 / 워크스페이스에 대한 내용이었습니다.
감사합니다.👍
Author And Source
이 문제에 관하여(Golang 기초 (12) : 패키지 & 모듈 & 워크스페이스에 대하여), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@vamos_eon/go-pkg-mod-work저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)