안녕하세요, 주니어 개발자 Eon입니다.
이번 포스트는 패키지 & 모듈 & 워크스페이스에 관한 내용입니다.
Golang의 코드를 묶는 단위이며, 모든 코드는 패키지에 속해야 합니다.
package main func main(){...}
main 패키지
를 선언할 수 있고,main()
함수를 작성할 수 있습니다.
main 패키지
가 가지는 의미는 다음과 같습니다.
'프로그램의 시작 패키지'
모든 Golang 프로그램은 main 패키지의 main()함수부터 실행됩니다.
(아래에서 다루지만 패키지 초기화 함수와는 다릅니다.)
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"
특정 패키지의 초기화가 필요한 경우, 위와 같이 직접적으로 사용하지 않아도 특정 패키지를 포함할 수 있습니다.
같은 패키지 내의 모든 소스코드는 안의 자원을 공유합니다.
${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 함수보다도 먼저 호출됩니다.
모듈은 종속성 관리를 위해 Golang이 지원하는 것입니다.
모듈은 패키지의 모음이며, 모듈 파일이 있는 곳이 패키지의 루트 경로가 됩니다.
(모듈 : 패키지 = 1 : N)
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/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.mod
에 대한 내용을 다뤘습니다.
외부 패키지를 포함하여 빌드를 하고 나면 go.sum
파일이 생성됩니다.
go.sum
은 외부 패키지의 버전에 대한 checksum
을 모아둔 파일입니다.
go.sum
파일이 없으면 빌드할 때 다시 생성합니다.
Golang 코드가 짜여지고 관리되는 공간입니다.
권장 방법은 링크를 참고하시면 됩니다.
https://go.dev/doc/gopath_code#Workspaces
문서와는 조금 다르게, 조금 더 간단하게 사용할 수도 있습니다.
/home/user/
아래에 workspace
로 사용할 디렉터리를 만들고 사용할 수 있습니다.
/home/user/awsome-project/
(단일 프로그램 또는 프로젝트)
/home/user/go-practice/
(여러 프로그램 또는 프로젝트)
위와 같이 여러 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
를 만들고 관리할 수 있습니다.
Vscode
를 사용하시는 분이라면 연습용 디렉터리 내에 여러 프로그램을 작성하고 테스트하고 싶을 겁니다.
하지만 Vscode
의 Explorer Folder 내에 같은 이름의 모듈이 존재하면 빨갛게 표시됩니다.
이는 테스트할 때마다 불편하게 합니다. (빌드도 정상적으로 되는데 괜히 마음 불편한..)
go mod init main
명령어로 간단하게 모듈 이름 상관없이 짓고 빌드하고 싶을 때 말입니다.
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
${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
파일이 생성되며 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
이상 패키지 / 모듈 / 워크스페이스에 대한 내용이었습니다.
감사합니다.👍