sachaos.md
----------

go run で実行されているかどうかを判定する

## 結論

厳密な判定方法は見つけることができませんでした。 うまい方法があったら教えていただけるとありがたいです。

こんな感じの関数ができました。

func RunningThroughGoRun() bool {
	executable, err := os.Executable()
	if err != nil {
		return false
	}

	goTmpDir := os.Getenv("GOTMPDIR")
	if "" != goTmpDir {
		return strings.HasPrefix(executable, goTmpDir)
	}

	return strings.HasPrefix(executable, os.TempDir())
}

## 解説

### WORK ディレクトリについて

go run を実行するとビルドが実行され、一時的なディレクトリにバイナリが作成され、 そのバイナリが実行されます。

-x をつけて実行すると、内部で実行されたコマンドを表示してくれます。

$ go run -x main.go
// これが一時的なディレクトリ
WORK=/var/folders/y3/t2g2qt4s6sxbptdqcf9t_s9r0000gn/T/go-build533629062

// 以降の成果物は WORK 以下に保存される
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=/Users/sachaos/Library/Caches/go-build/06/06117ec80775cccc4e5113a6e6e7d7335ee58db7782d79139373a9b8082887a5-d
packagefile ... 省略
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/Cellar/go/1.15.5/libexec/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/main -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=GU0rFYws22Bz55p-zUYM/Y7Es9m9kLVKFd9e0pny7/94GFXHKU4yEPQNxMBO1J/GU0rFYws22Bz55p-zUYM -extld=clang /Users/sachaos/Library/Caches/go-build/06/06117ec80775cccc4e5113a6e6e7d7335ee58db7782d79139373a9b8082887a5-d
$WORK/b001/exe/main

厳密な判定ではありませんが、この一時的なディレクトリ WORK に存在しているバイナリが実行されていたら、 go run で実行されていると考えて良さそうです。

### WORK のパスはどのように決定されるか

この WORK のディレクトリのパスは以下のコードで指定されているようです。

		tmp, err := os.MkdirTemp(cfg.Getenv("GOTMPDIR"), "go-build")

https://github.com/golang/go/blob/59bfc18e3441d9cd0b1b2f302935403bbf52ac8b/src/cmd/go/internal/work/action.go#L255

GOTMPDIR が指定されていれば、それが使われます。 指定されていなければ os.MkdirTemp のデフォルトの os.TempDir が使われるようです。

### どこにあるバイナリが実行されているのか判定する

os.Executable を利用すると現在実行されているプロセスを開始した実行ファイルのパスを取得することができます。

この実行ファイルのパスが、 WORK 以下にあるのであれば go run で実行されていると判定できそうです。

### RunningThroughGoRun 関数を実装し試す

以下のような形で go run で実行されているかどうかを判定するコードを実装してみました。

package main

import (
	"fmt"
	"os"
	"strings"
)

func RunningThroughGoRun() bool {
	executable, err := os.Executable()
	if err != nil {
		return false
	}

	goTmpDir := os.Getenv("GOTMPDIR")
	if "" != goTmpDir {
		return strings.HasPrefix(executable, goTmpDir)
	}

	return strings.HasPrefix(executable, os.TempDir())
}

func main() {
	if RunningThroughGoRun() {
		fmt.Println("Running through go run!")
	} else {
		fmt.Println("Running binary!")
	}
}
$ go build main.go && ./main
Running binary!

$ go run main.go
Running through go run!

ちょっと面白いですね。

繰り返しますが、厳密な判定にはなりませんが、私のユースケースは満たしそうでしたのでここで終了します。

## 参考