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")
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!
ちょっと面白いですね。
繰り返しますが、厳密な判定にはなりませんが、私のユースケースは満たしそうでしたのでここで終了します。