Goをソースからインストールする

Go勉強の手始めに、まずはソースからインストール。

# 環境

 - Fedora release 31

# インストール手順

ソースからインストールする際の公式ドキュメントは[こちら](https://golang.org/doc/install/source)。

## ツールチェインの入手

GoのコンパイラツールチェインはGoで記述されており、ツールチェインをビルドするにはGoのコンパイラが必要になる。

Goのコンパイルを行うには、この最初のビルドを行うツールチェイン (ブートストラップツールチェイン) が必要になる。
ブートストラップツールチェインもソースからコンパイルすることができるが、今回はビルド済のバイナリを入手する。

(Goをコンパイルするためにコンパイル済みのバイナリを手に入れるという行為には矛盾を感じるが) 
ダウンロードページからバイナリを入手して展開する。

```bash
$ tar -xvzf go1.14.2.linux-amd64.tar.gz
$ mv go go1.14
```

環境変数をセットする。Goのコンパイラはツールチェインをビルドするためのブートストラップツールチェインを`$GOROOT_BOOTSTRAP`から探す。

```bash
$ export GOROOT_BOOTSTRAP=/home/hidakanoko/Dev/go/go1.14
```

## コンパイル

Goのソースコードを適当な位置にクローンする。

```bash
$ cd /home/hidakanoko/Dev/go/
$ git clone https://go.googlesource.com/go goroot
```

適当なバージョンをチェックアウトする。

```bash
$ cd goroot
$ git checkout go1.14.2
```

コンパイルを実行する。テストなども走るらしく、時間がかかる。

```bash
$ cd src
$ ./make.bash
```

環境変数を設定して`go`コマンドが通るか確認。

```bash
$ export PATH="$PATH:/home/hidakanoko/Dev/go/goroot/bin"
$ export GOROOT="/home/hidakanoko/Dev/go/goroot"
$ go version
go version go1.14.2 linux/amd64
```

無事成功。

# 動作確認

Goのソースをコンパイルしてみる。

```test.go
package main

import "fmt"

func main() {
	fmt.Println("hello, world")
}
```

`go run`でソースのコンパイルと実行。

```bash
$ go run ~/Dev/go/test/test.go 
hello, world
```

コマンドのヘルプは`go help run`でみられる。

```bash
$ go help run
usage: go run [build flags] [-exec xprog] package [arguments...]

(...省略)
```

# ソースを弄ってコンパイルしてみる

せっかくなのでGoのソースを弄ってみる。

`fmt.Println`は引数で与えられた文字列に改行を付与して標準出力に書き出す。
https://golang.org/pkg/fmt/#Println

Printlnは`./src/fmt/print.go`に定義されている。

```bash
$ find ./ -name "*.go" | xargs grep "func Println"
(...省略)
./src/fmt/print.go:func Println(a ...interface{}) (n int, err error) {
(...省略)
```

`Println`の定義は次のようになっている。

```src/fmt/print.go
 273 func Println(a ...interface{}) (n int, err error) {
 274         return Fprintln(os.Stdout, a...)
 275 }
```

`a ...interface{}`はあらゆる引数を受け付ける可変長引数。可変長引数の実体は`slice`らしい。
https://golang.org/ref/spec#Passing_arguments_to_..._parameters

標準出力`os.Stdout`と受け取った`a...`を引数にして`Fprintln`を呼び出している。

ここに与えられた引数に草を生やす関数`Printlnw`を追加してみる。

```src/fmt/print.go
 277 // Printlnw formats using the default formats for its opedands and writes to standard output with weed.
 278 func Printlnw(a ...interface{}) (n int, err error) {
 279         b := append(a, "www")
 280         return Fprintln(os.Stdout, b...)
 281 }
```

`append`は`slice`にアイテムを追加して新しい`slice`を生成する。
https://golang.org/pkg/builtin/#append

コンパイルしてみるとAPI Checkのあたりで失敗する。どうも公開する関数を`api/`ディレクトリにバージョン毎に定義しているらしのだが、詳細はちょっと調べたがよくわからなかった。

```bash
$ ./all.bash

(...省略)

##### API check
Error running API checker: exit status 1
Go version is "go1.14.2", ignoring -next /home/hidakanoko/Dev/go/goroot/api/next.txt
+pkg fmt, func Printlnw(...interface{}) (int, error)
exit status 1
2020/05/10 11:18:02 Failed: exit status 1
go tool dist: FAILED
```

しょうがないので`make.bash`でビルドだけ実行。

```bash
$ ./make.bash
(...省略)
Building packages and commands for linux/amd64.
---
Installed Go for linux/amd64 in /home/hidakanoko/Dev/go/goroot
Installed commands in /home/hidakanoko/Dev/go/goroot/bin
```

ビルド成功したので、テストプログラムを修正して実行してみる。

```test.go
package main

import "fmt"

func main() {
        fmt.Printlnw("hello, world")
}
```

コンパイルと実行。

```bash
$ go run ~/Dev/go/test/test.go 
hello, world www
```

成功！！
Goの実行環境をDockerなどで用意する際は、こっそり草生やすバージョンと差し替えてメンバーの心をほっこりさせようと思う（怒られる）