20200604のGoに関する記事は2件です。

Golang環境整える。Windows(WSL2、WSL)、Mac、Docker、VSCode(+Remote Docker)Golint・・・

motivation

今、仕事にメインで使ってるPCが3台あり、それぞれの環境が異なります
デスクトップ:Windows10 Pro InsidePreview + WSL2
RazerBlade ゲーミングPC: Windows10 Pro + WSL
Macbook Pro: Catalina
また、近々、ゲーミングPCの買い替え、デスクトップの再インストール等を予定していて
それぞれの環境構築を色々と試した結果、現在の環境構築のメモ

一応、WSLはUbuntu18.04 を想定してますが、他のLinuxでもだいたい同じはず

完全に自分用です

Windows: WSL&Docker

Goはサーバインフラで使われることが多いので、たいていの場合はDockerが必要かと思います
また、仕事では他の人はBashでスクリプトを書いてくると思います
そのため、Bashはほぼ必須といってよいです
もちろん古風なVirtualBoxにLinuxをインストールしてもいいですが
WSLやWSL2を使う方が現代的で好みです

WSL、WSL2、Dockerまわりの環境構築は下記にメモしてあります

https://qiita.com/YukiMiyatake/items/c7896a0fc5abfa6c2300
https://qiita.com/YukiMiyatake/items/73c7d6c4f2c9739ebe60

Goのインストール

公式の通りに行います
https://golang.org/doc/install

以前はgoenv等のバージョニングシステムを使っていたのですが
なぜかVSCode等の相性がわるく(goenvが設定した$GOPATH等を見てくれない事がある)
使うのをやめました。

しかも公式でバージョニングの方法も書いてありました

インストール

まずはGoがインストールされていたら消します

その後、インストールしますが、パッケージからインストールするとバージョンが古いため
公式が配布しているイメージからインストールします
公式ページには下記のスクリプトが書いてあります

tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz

OSや取得バージョンに合わせてダウンロードファイルを変更しますが
具体的には下記のようにします(Goのバージョンは現時点で最新の 1.14.3)

Windows(WSL、WSL2)
wget https://dl.google.com/go/go1.14.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.14.3.linux-amd64.tar.gz
Mac
wget https://dl.google.com/go/go1.14.3.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.14.3.darwin-amd64.tar.gz

これで、 /usr/local/go にインストールされる。

念のため go version で正しくインストールされているか確認を行い
go env で、GOの環境変数を確認しましょう

おそらく、 GOROOT="/usr/local/go" GOPATH="$HOME/go"
になっているはず

GOROOTの方にGoのバイナリが入り、GOPATHにはダウンロードしたパッケージやバイナリが入る
ので、ここにパスを通しておく

たとえば.bashrc等に

bash: bashrc
export GOPATH="$HOME/go"
export PATH=$PATH:$GOPATH/bin
export PATH=$PATH:/usr/local/go/bin

と書く(GOPATHはおそらくデフォルトで指定されてるが念のためかいてる)
$GOPATH/binにも通しておく

バージョニング

公式によると

go get golang.org/dl/go1.10.7
go1.10.7 download

go getで任意のバージョンをインストールし

go1.10.7 version

で、使うバージョンを指定できるらしい。
非常に便利だ

ただし $HOME/go/bin やsrcが同じなため、複数バージョンのファイルが混在する事になるので
以前はGoのバージョンごとに $GOPATHを切り替えていたが
他の人の話をきくかぎり、混在しても問題がおきた事がないとの話なので
GOPATHをわけない方針に変更した

gcc

GoのパッケージにはGCCを使うものも多いためGCCをインストールする
Clang派ではあるが、手軽にaptやbrewを使ってGCCをインストールする

makeのインストール

GoのプロジェクトはMakeで行う事が多いので
パッケージマネージャを使いMakeをインストールしておく

VSCode

VSCodeのインストールはWindowsもMacもパッケージでインストールした

リモートWSL: Windows

MacではデフォルトでBashになっていて、ネイティブにGoをインストールしているので対応なくてよい

Windowsは今回はWSL上で動かす
リモートWSLを使うと、WSL上でそのままデバッグしたり実行したり非常に便利なので
VSCode+リモートWSLをお勧めする

WSL2ではなぜか、VSCodeが初期状態でリモートWSLが有効になっていた気がするが・・
左下にWSLアイコンが出ない場合は、VSCode上からリモートWSLをインストールしよう

リモートWSLでプロジェクトを開く

必ずリモートWSLで開くこと!
左下のWSLボタンを押し、Remote-WSL:NewWindowで新規ウインドウを開き、OpenFolderする
気を付けるのは、この時にWSLのパスを入力する事。
WindowsパスやShowLocalで選ぶと、リモートWSLにならない

プラグイン(Extensions)導入

VSCodeのExtensionsメニューより、とりあえずGoプラグインをインストールする

その後、Ctrl+Shift+Pでコマンドパレットを開き GO:Install/Update Toolsを選択

image.png

必要なものにチェックボックスをつけ、インストールする

$ ls $GOPATH/bin

dlv         go-outline  gocode        godef     gofmt      golint        gopkgs  gorename  impl
fillstruct  go-symbols  gocode-gomod  godoctor  goimports  gomodifytags  goplay  gotest    guru

このように$GOPATH/binにインストールされる

ビルド、デバッグ実行、テスト等

適当なプロジェクトを使い
まずはVSCodeのターミナルからテスト

go buid .
./実行ファイル
go test .

VSCodeからデバッグ実行するために、ブレークポイントをおきF5キー押しデバッグ出来る事の確認
動かない場合は Launch.jsonを修正する

例えば、プロジェクトに /server /client の2つのプロジェクトがある場合は

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Client",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "program": "${workspaceFolder}/client"
        },

        {
            "name": "Server",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "program": "${workspaceFolder}/server"
        }
    ]
}

こんな感じで書いておく。細かい構文は調べてね・・

単体テストの確認

Goファイルの関数を右クリックして、コンテキストメニューに
Go:Generate Unit Tests For Function
がある事を確認し、クリックすると単体テストコードを自動生成してくれる
そして、テスト側のコードに移動し、関数の上に run test | debug testが表示される
それをクリックし、関数単位でテストの実行、デバッグの実行が出来る事を確認する

Extensionsで、Go Test Explorerをインストールする。
UIを使って個別にテストを実行したり、Faildの確認したり出来て多少便利だ

その他パッケージ

あまりパッケージに詳しくないため、便利なパッケージを日々探している
単純に出力が色分けされるだけで快適になったりする

grpc-go

gRPCを使う場合には必須になる
go get -u google.golang.org/grpc

colorgo

go buildの出力がカラフルになる
go get -u github.com/songgao/colorgo

gotests

go testの出力がカラフルになる
go get -u github.com/cweill/gotests/...

gocheck

go testより高機能
go get -u gopkg.in/check.v1

sql-migrate

DBのマイグレーションツール
go get -u github.com/rubenv/sql-migrate/...

mockgen

インターフェースモックを作成
go get -u github.com/golang/mock/mockgen

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GOプログラムの起動手順について

問題

GO のプログラムはmainという package のmain()方法で始めるのがよくご存知でしたが、その前に何が起こったのかな?これをちょっと調べて行きます。

実行環境

  • Ubuntu 18.04
  • Go 1.14

もっともシンプルな GO プログラム

まずはこの何もしないプログラムを用意します

main.go
package main

func main() {}
$ go build main.go

main.mainを探す

ELFを調べてみる

まずはコンパイルされたバイナリをreadelfでみてみま

$ readelf -h main
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4552c0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          456 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         7
  Size of section headers:           64 (bytes)
  Number of section headers:         25
  Section header string table index: 3

その中のEntry point addressは実行の入口です、そのアドレスは0x4552c0。じゃ次はこのアドレスを見てみよう。

$ objdump -d main | grep 4552c0
00000000004552c0 <_rt0_amd64_linux>:
  4552c0:   e9 4b c4 ff ff          jmpq   451710 <_rt0_amd64>

0x4552c0のとこは_rt0_amd64_linuxという関数ですね、そしてすぐ_rt0_amd64に移行するようです。

_rt0_amd64はどこ?

ソースコードに_rt0_amd64を検索したら、asm_amd64.sに見つけました。

src/runtime/asm_amd64.s
TEXT _rt0_amd64(SB),NOSPLIT,$-8
    MOVQ    0(SP), DI   // argc
    LEAQ    8(SP), SI   // argv
    JMP runtime·rt0_go(SB)

そしてすぐruntime·rt0_goにジャンプしました。もうちょっと追ってみれば、これを見つけた

src/runtime/asm_amd64.s
...
    MOVL    16(SP), AX      // copy argc
    MOVL    AX, 0(SP)
    MOVQ    24(SP), AX      // copy argv
    MOVQ    AX, 8(SP)
    CALL    runtime·args(SB)
    CALL    runtime·osinit(SB)
    CALL    runtime·schedinit(SB)

    // create a new goroutine to start program
    MOVQ    $runtime·mainPC(SB), AX     // entry
    PUSHQ   AX
    PUSHQ   $0          // arg size
    CALL    runtime·newproc(SB)
    POPQ    AX
    POPQ    AX

    // start this M
    CALL    runtime·mstart(SB)
...
DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL   runtime·mainPC(SB),RODATA,$8
...

runtime·argsruntime·osinitruntime·schedinitを次々CALLしてから、runtime·mainPCを引数としてruntime·newprocCALLしました。このentryで注釈したruntime·mainPC実はruntime·mainとして定義したものです。

runtime.mainruntime.newproc

またソースにnewprocを検索したら、proc.goにたどり着いた

src/runtime/proc.go
...
// Create a new g running fn with siz bytes of arguments.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
//
// The stack layout of this call is unusual: it assumes that the
// arguments to pass to fn are on the stack sequentially immediately
// after &fn. Hence, they are logically part of newproc's argument
// frame, even though they don't appear in its signature (and can't
// because their types differ between call sites).
//
// This must be nosplit because this stack layout means there are
// untyped arguments in newproc's argument frame. Stack copies won't
// be able to adjust them and stack splits won't be able to copy them.
//
//go:nosplit
func newproc(siz int32, fn *funcval) {
...

コメントによると、runtime.newprocは一つの goroutine を起動してfnを実行するということですね。そしてここのfnは先のruntime.mainのようです。次はruntime.mainを見てみましょ。

src/runtime/proc.go
...
// The main goroutine.
func main() {
    g := getg()
...
    fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
    fn()
...

runtime.mainはいろんなことをやっていて、main_mainをコールした。このmain_mainはもしかして。。。

src/runtime/proc.go
...
//go:linkname main_main main.main
func main_main()
...

そうか、ここはgo:linknameという Compiler Directive に通してmain_mainmain.mainに変換しました。

まとめ

  • GO プログラムのほんとのエントリーポイントは_rt0_amd64_linuxです(Linuxの場合)。
  • いろんな初期化はxxxinitでやっています。特にruntime.schedinit
  • main.mainの実行も goroutine の一つです

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む