$ vi ~/.vimrc
... 중간 생략 ...
" ------------------------------------------
" vim-go 설정
" -----------------------------------------
filetype plugin indent on
" vim-Plug Plugin List
call plug#begin('~/.vim/plugged')
" 자동완성을 해주는 Plugin.
" :CoCInstall coc-<lang> 으로 지원되는 Language 설치가 가능
" NOTE: 내 경우는 아래 neoclide/coc.nvim을 설치하지 않았다.
" 왜냐하면, 나는 node.js를 사용하지 않기 때문.
" Plug 'neoclide/coc.nvim', {'branch': 'release'}
" 많이 사용되는 plugin list
Plug 'majutsushi/tagbar'
Plug 'vim-airline/vim-airline'
Plug 'tpope/vim-fugitive'
" vim-go 사용을 위한 Plugin
Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
" 자동 완성 Plugin 이다.
Plug 'SirVer/ultisnips'
Plug 'maralla/completor.vim'
" Misc utilities
Plug 'milkypostman/vim-togglelist'
Plug 'AndrewRadev/splitjoin.vim'
Plug 'mhinz/vim-startify'
" preview plugin
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } " fzf 사용시 꼭 설치; ~/.fzf 에서 ./install --all 실행해야 함.
Plug 'junegunn/fzf.vim'
" Initialize plugin system
call plug#end()
... 중간 생략 ...
${HOME}/.vimrc 파일을 저장하고, 아래와 vim 편집기를 실행 후 vim 내부 명령을 수행한다.
$ vim
... 중간 생략 ...
:PlugInstall
수십 초 정도 시간이 흐르면서 plugin을 설치할 것이다. (시간이 많이 걸리니까 다른 일을 하면서 신경을 끄는게 좋다)
vim-go 관련 설정 (개인 취향에 따라 설정 값 정하기)
아래 vim-go 관련 설정은 개인 취향에 따라 값을 정하면 된다.
$ vim ~/.vimrc
... 중간 생략 ...
" -------------------------------------------------------
" NOTE: *.go 파일을 저장할 때 Auto formatting하고,
" 관련 go package를 자동으로 .go 소스 코드 파일에 추가해준다.
let g:go_fmt_command = "goimports"
let g:go_list_type = "quickfix"
let g:go_addtags_transform = "camelcase"
let g:go_autodetect_gopath = 1
let g:go_highlight_types = 1
let g:go_highlight_fields = 1
let g:go_highlight_functions = 1
let g:go_highlight_function_calls = 1
let g:go_highlight_extra_types = 1
let g:go_highlight_generate_tags = 1
let g:go_highlight_operators = 1
let g:go_auto_type_info = 1
let g:go_auto_sameids = 1
" NOTE: 문서를 Popup window 형태로 보여주기 (:GoDoc 명령)
let g:go_doc_popup_window = 1
" NOTE: go source code 작성할 때, 실시간으로 자동 완성할 추천 문장을 보여줌.
let g:completor_filetype_map = {}
let g:completor_filetype_map.go = {'ft': 'lsp', 'cmd': 'gopls - remote=auto'}
" NOTE: quickfix 이동 및 open/close
nnoremap <C-n> :cnext<CR>
nnoremap <C-p> :cprevious<CR>
nnoremap <LocalLeader>q :call ToggleQuickfixList()<CR>
" NOTE: Coverage toggle
nnoremap <LocalLeader>c :GoCoverageToggle<CR>
" NOTE: Go 프로그램을 build, run, test하는 명령에 대한 설정
autocmd FileType go nnoremap <Tab>b :GoBuild<CR>
autocmd FileType go nnoremap <Tab>r :GoRun<CR>
autocmd FileType go nnoremap <Tab><Tab>r :GoRun %<CR>
autocmd FileType go nnoremap <Tab>t :GoTest<CR>
autocmd FileType go nnoremap <Tab><Tab>t :GoTestFunc<CR>
autocmd FileType go nnoremap <Tab>c :GoCoverageToggle<CR>
" -------------------------------------------------------
... 중간 생략 ...
Client 장비에 network port가 여러개 있는 경우, 특정 network port를 지정하여 IP 패킷을 전송하고 싶을 때가 있다.
이럴 때, source IP address를 binding하면 특정 network port를 통해 IP 패킷이 전송된다.
참고: 일반적으로 Target IP address로 가기 위한 routing path 및 network port는 OS에 있는 Routing table을 통해서 자동으로 결정된다. 그러나 Target IP address로 가기 위한 routing path가 1개가 아닌 2개 이상인 경우에는 어느 network port를 통해서 IP 패킷이 나갈지 예측하기 어렵다.
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
)
func main() {
##
## NOTE: 14.33.80.179를 Source IP address로 지정한다. (즉, Source IP address binding)
##
localAddr, err := net.ResolveIPAddr("ip", "14.33.80.179")
if err != nil {
panic(err)
}
localTCPAddr := net.TCPAddr{
IP: localAddr.IP,
}
d := net.Dialer{
LocalAddr: &localTCPAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: d.Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
webclient := &http.Client{Transport: tr}
// Use NewRequest so we can change the UserAgent string in the header
req, err := http.NewRequest("GET", "https://www.naver.com", nil)
if err != nil {
panic(err)
}
res, err := webclient.Do(req)
if err != nil {
panic(err)
}
fmt.Println("DEBUG", res)
defer res.Body.Close()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Printf("%s", string(content))
}
go source code를 작성하자. (아래는 예시를 위해서 간단하게 만든 것이니까, 참고만 할것 !!!)
$ cat main.go
package main
import (
"fmt"
)
func main() {
getAppVersion()
// My Main Routine ...
fmt.Printf("My Main Routing ...\n")
}
$
`go build -ldflags "-X ... -X ... -X ..."` 이런 식으로 입력한 외부 상수를 go source code 내부에서 참조(reference)할 수 있도록 아래와 같이 변수를 선언하고, 사용하자 !!!
package main
import (
"fmt"
)
var (
appVersion string
buildDate string
devNote string
)
func getAppVersion() {
fmt.Printf("App Name : almighty\n")
fmt.Printf("Version : %s\n", appVersion)
fmt.Printf("Build Date : %s\n", buildDate)
fmt.Printf("Dev Note : %s\n", devNote)
}
위와 같이 go source code가 작성되었다면, 아래와 같이 build하고 실행 바이너리 파일을 실행해보자.
$ my_go_build.sh
$ my_example_app
App Name : almighty
Version : 0.1.0
Build Date : 2022-07-05 16:18:12
Dev Note : New Feature ABC is added
My Main Routing ...
$
참고: Go Module과 Package를 사용하는 방법이 Go Version에 따라 다를 수 있다. 아래 예제는 Go 1.17을 이용한 예제이고, 아마 Go 1.11 이상을 사용하는 경우라면 아래 설명이 잘 맞을 것이다.
여담: 나는 본업이 Go Language를 사용해서 개발하는 개발자가 아니다보니, 2~3년에 한번 잠깐 GoLang을 이용해서 개발하려고 하면 Go Package와 Module 때문에 시간을 허비하게 된다. 그래서 오늘은 기왕 스터디한 것을 2~3년 뒤에 또 GoLang을 사용할 일이 있을 경우, 쉽게 Package와 Module 개념을 습득하기 위해 예제를 통해서 메모를 할까 한다.
Go PKG를 다루기 위해서는 크게 3가지 방식이 있다.
방식 A
$GOPATH 경로를 설정하고, 이 $GOPATH 경로 안에서만 Package를 사용하는 방법. (암묵적 Module 사용하기)
방식 B <-- 가장 많은 개발자가 선호하는 방식이 아닐까 생각한다.
Module을 명시적으로 선언(go mod init x.y.z)하고, 내가 원하는 폴더 어디에서든 Go Source Code를 작성하는 방법.
방식 C
go ven를 이용해서 현재 디렉토리 하위에서 모든 Package를 사용하는 방법.
각 Package 사용 방식에 대해서 아래에서 세부적으로 다룬다.
$GOPATH 경로를 선언하여 Package를 사용하는 방식
설명보다는 예제 코드를 보는 것이 좋을 듯~~~
$ vi ~/.bash_profile
...
export GOPATH=$(go env GOPATH)
...
$ . ~/.bash_profile
$ echo $GOPATH
/Users/sejong/go
$ cd $GOPATH
##
## 이제부터는 $GOPATH/src 디렉토리 밑에서만 코드를 작성해야 한다.
##
$ mkdir -p src/myexample
$ cd src/myexample
$ mkdir myPkgAlpha
$ cd myPkgAlpha
$ vi example.go
package myPkgAlpha
import "fmt"
# 주의: 패키지 외부에서 참조할 수 있도록 함수 이름은 대문자로 시작해야 한다.
func MyExamplePrint() {
fmt.Println("My Alpha PKG")
}
$ cd ..
$ vi main.go
package main
import (
"fmt"
"myexample/myPkgAlpha"
)
func main() {
fmt.Println("This is main package.")
myPkgAlpha.MyExamplePrint()
}
$ go mod init myexample
$ go mod tidy
$ go build
$ ./myexample
This is main package.
My Alpha PKG
$ tree
.
├── go.mod
├── main.go
├── myPkgAlpha
│ └── example.go
└── myexample
$
$GOPATH 경로가 아닌, 나만의 별도의 Project 폴더에서 Go Module 사용하는 방식
Go module을 사용하면 $GOPATH 경로가 아닌 아무 곳(디렉토리)에서 Go Source Code를 작성할 수 있는 자유가 생긴다.
긴 설명은 생략하고, 아래 예제처럼 Go Package와 Module을 사용하면 된다.
##
## 새로운 프로젝트를 위한 폴더를 만들자~
##
$ mkdir go-mod-pkg-example
$ cd go-mod-pkg-example
##
## Go Package를 만들자. (myPkgAA)
## 주의사항: package 이름과 folder 이름을 동일하게 작성해야 한다.
##
$ mkdir myPkgAA
$ cd myPkgAA
##
## 아래와 같이 myPkgAA 패키지에 대한 코드를 작성한다.
##
$ vi example.go
package myPkgAA
const (
COMPANY = "Sejong Inc"
LOCATION = "Seoul Korea"
)
##
## Package가 1개만 있으면 심심하니까, 하나 더 만들자. (mypkg_bb)
##
$ cd ..
$ mkdir mypkg_bb
$ cd mypkg_bb
##
## 아래와 같이 mypkg_bb 패키지에 대한 코드를 작성한다.
##
$ vi example.go
package mypkg_bb
import (
"fmt"
)
// NOTE: 함수 이름은 대문자로 시작해야 한다.
func DoSomething(company string, location string) {
fmt.Println("[In mypkg_bb]")
fmt.Println(" Company :", company)
fmt.Println(" Location :", location)
}
##
## 위에서 작성한 Package를 사용하는 Go Module Code를 작성하자.
##
$ go mod init myexample.cncf/sejong/myexample
$ vi main.go
// NOTE: package 이름을 main 으로 한다.
package main
// NOTE: 이 module의 이름이 "myexample.cncf/sejong/myexample" 이었기 때문에
// myPkgAA 패키지를 import하려면 아래처럼 경로를 작성해야 한다.
import (
"myexample.cncf/sejong/myexample/myPkgAA"
"myexample.cncf/sejong/myexample/mypkg_bb"
)
func main() {
mypkg_bb.DoSomething(myPkgAA.COMPANY, myPkgAA.LOCATION)
}
## 솔직히, 위 예제 패키지(로컬 패키지)만 이용하는 경우에는 아래 tidy 명령을 필요 없다.
## 그러나 main.go 파일에서 외부 pkg를 import 한 것도 포함했다면,
## 아래처럼 tidy 명령을 수행해야 한다.
$ go mod tidy
$ go build
$ ./myexample
[In mypkg_bb]
Company : Sejong Inc
Location : Seoul Korea
$ tree
.
├── go.mod
├── main.go
├── myPkgAA
│ └── example.go
├── myexample
└── mypkg_bb
└── example.go
$
Vendor 방식의 패키지 관리
vendor 디렉토리에 특정 버전의 외부 패키지들을 저장 시킨 뒤 빌드에 참여시킴으로써 버전 일관성 문제를 해결 할 수 있다.
그리고 다운로드 불가한 외부 패키지를 어떻게든 한번만 구할 수만 있다면 다운로드 불가 문제도 피할 수 있다.
Container Image처럼 내가 작성한 Go Module 소스 코드가 있는 폴더에 의존성이 있는 모든 Package를 vendor/ 폴더 디렉토리에 다운로드 받는 다는 것이 가장 큰 특징이다.
Cobra is a library providing a simple interface to create powerful modern CLI interfaces similar to git & go tools. Cobra is also an application that will generate your application scaffolding to rapidly develop a Cobra-based application.
위 Cobra Web Docs 문서에서 소개하는 것처럼 Cobra는 library이면서, application scaffoling을 만들어주는 개발 도구이다.
그래서 단순하게 library reference만 읽고 사용법을 익히는 것이 아니라 Cobra 도구를 이용해서 scaffolding을 만들고, 그 scaffolding 안에서 나의 logic을 추가해야 한다.
Cobra의 개발 절차만 잘 따라하면, 시간을 팍팍 줄여가면서 CLI를 붕어빵 찍어내듯이 만들 수 있을 것 같은 느낌적인 느낌이 들었다.
막상 go source code를 작성하고 compile하려고 생각해보니, 실제로 실행 파일(executable file)을 돌릴 대상 장비가 x86 cpu를 사용하는 경우가 대부분이라서 cross compile 필요성을 느꼈다.
아직 한번도 go cross compile을 하지 않았던터라, 이번 기회에 짧게 메모를 해본다.
cross compile 지원되는 OS와 CPU architecture 확인
$ go tool dist list
aix/ppc64
android/386
... 중간 생략 ...
darwin/amd64 <-- Intel Mac을 사용하는 경우
darwin/arm64 <-- M1 Mac을 사용하는 경우
...
linux/386
linux/amd64 <-- Intel Linux를 사용하는 경우 (아마 대부분 이 경우가 아닐까?)
linux/arm
linux/arm64
... 중간 생략 ...
windows/386
windows/amd64
windows/arm
windows/arm64
$
go cross compile
Case: Linux OS & Intel CPU
M1 Macbook에서 Linux OS에서 돌릴 executable file을 build하는 방법이다.
GOOS=linux GOARCH=amd64환경 변수만 설정하고, go build 명령을 수행하면 Linux OS에서 돌아가는 실행 파일이 만들어진다.
$ go mod init andrew.space/myapp
$ go mod tidy
$ GOOS=linux GOARCH=amd64 go build
$ ls
myapp
$
Case: Linux OS & ARM CPU
Intel Macbook에서 M1 Macbook에서 돌릴 executable file을 build하는 방법이다.
GOOS=darwin GOARCH=arm64환경 변수만 설정하고, go build 명령을 수행하면 M1 Mac OS에서 돌아가는 실행 파일이 만들어진다.
(여담이지만, 이미 M1 Mac OS는 OS 자체적으로 Intel->ARM 구조로 변환해주는 기능이 있어서 굳이 이렇게까지 열심히 Cross compile을 하지 않아도 된다)
$ go mod init andrew.space/myapp
$ go mod tidy
$ GOOS=darwin GOARCH=arm64 go build
$ ls
myapp
$
Case: 다양의 OS와 CPU에 대한 일괄적으로 Cross Compile하기
매번 대상 OS와 CPU에 맞춰서 Cross Compile하기 귀찮다면, 아래와 같이 모든 OS, CPU에 대해서 Cross compile하도록 script를 만든다.
#!/usr/local/bin/bash
archs=(amd64 arm64 ppc64)
for arch in ${archs[@]}
do
env GOOS=linux GOARCH=${arch} go build -o myapp_${arch}
done
Tip: .bashrc 파일에 GO 환경 변수 설정
위에서 설명한 $GOOS, $GOARCH 환경 변수를 매번 타이핑하기 싫다면
아래처럼 .bashrc 파일에 한번만 기록해놓자!!!
$ cat ~/.bashrc
... 중간 생략 ...
##
## For GoLang
##
export GOPATH=$(go env GOPATH)
export GO111MODULE=on
export GOOS=linux
export GOARCH=amd64
... 중간 생략 ...
$