Golang 기초 (12) : 패키지 & 모듈 & 워크스페이스에 대하여

38090 단어 golang기초입문gogo

안녕하세요, 주니어 개발자 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


이상 패키지 / 모듈 / 워크스페이스에 대한 내용이었습니다.
감사합니다.👍

좋은 웹페이지 즐겨찾기