WSLENVでWSLとWindowsの環境変数を共有する(Go開発環境編)

Posted on May 2, 2018

見た目は地味だが役に立つ

Windows 10 April 2018 Update (別名: バージョン1803)がリリースされました。タイムラインなど目立つ機能が注目されていますが、開発者支援系の機能、ツールも拡充されています。特に、WSL/Windowsの連携、相互運用まわりは着実に進化しています。そのうちのひとつが、このエントリーで紹介するWSLENVです。

WSLENVは、WSL/Windows間で環境変数を共有する仕組みです。ただ単純に共有するだけでなく、ルールに従って変換も行います。これが地味に便利。でも地味だから、あまり話題になっていない。なので具体例で紹介しよう、というのがこのエントリーの目的です。

TL;DR

英語が読めて、「あ、それ便利ね」とピンとくる人は以下を。

Share Environment Vars between WSL and Windows

Go開発環境を例に

前述のリンクでも紹介されていますが、Goの開発環境はWSLENVの代表的なユースケースです。GOPATHをいい感じにWSL/Windowsで共有できます。掘り下げていきましょう。

想定開発者像、ペルソナ

  • Windows端末を使っている
  • Go言語を使っている
  • CLIはbash/WSL中心
    • スクリプト書くならPowerShellもいいけど、インタラクティブな操作はbashが楽
    • アプリをDockerコンテナーとしてビルドするなど、OSSエコシステム、ツールとの連携を考慮
  • とはいえエディタ/IDEはWindows側で動かしたい、最近はVS Code中心

前提条件

  • WSL、WindowsそれぞれにGoを導入
    • バージョン管理のためにも、パッケージマネージャーがおすすめ
    • わたしはWSL(Ubuntu)でapt、WindowsではChocolateyを使ってGoを導入しています
  • GOPATHは %USERPROFILE%go とする
    • ユーザー名を tomakabeとすると C:\Users\tomakabe\go
    • setx GOPATH “$env:USERPROFILE\go” で設定
    • WSLでもこのディレクトリをGOPATHとする
  • VS Code + Go拡張をWindowsに導入
  • WindowsのCLIはPowerShellを利用

そぞろ歩き その1(WindowsでのGo開発)

では、何が課題で、WSLがどのようにそれを解決するか、見ていきましょう。

まず、Windowsで環境変数GOPATHを確認します。

PS C:\WINDOWS\system32> Get-ChildItem env:GOPATH

Name                           Value
----                           -----
GOPATH                         C:\Users\tomakabe\go

GOPATHに移動し、ディレクトリ構造を確認します。この環境にはすでにディレクトリbinとsrcがあり、binにはいくつかexeが入っています。VS CodeのGo拡張を入れると導入を促されるツール群は、ここに格納され、構文チェックや補完でVS Codeと連動します。

PS C:\WINDOWS\system32> cd C:\Users\tomakabe\go
PS C:\Users\tomakabe\go> ls


    ディレクトリ: C:\Users\tomakabe\go


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2018/05/02     11:10                bin
d-----       2018/05/02     11:06                src

PS C:\Users\tomakabe\go> ls .\bin\


    ディレクトリ: C:\Users\tomakabe\go\bin


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2018/05/02     11:10       14835200 dlv.exe
-a----       2018/05/02     11:09        4239360 go-outline.exe
-a----       2018/05/02     11:09        4045824 go-symbols.exe
-a----       2018/05/02     11:08       11094528 gocode.exe
-a----       2018/05/02     11:09        5708288 godef.exe
[snip]

サンプルコードのディレクトリへ移動し、中身を確認します。シンプルな挨拶アプリです。

PS C:\Users\tomakabe\go> cd .\src\github.com\torumakabe\work\
PS C:\Users\tomakabe\go\src\github.com\torumakabe\work> cat .\hello.go
package main

import "fmt"

func main() {
        fmt.Println("Hello Go on the new WSL")
}

ビルドして動かしてみましょう。Windows環境ではデフォルトで実行ファイルとしてexeが作られます。

PS C:\Users\tomakabe\go\src\github.com\torumakabe\work> go build .\hello.go
PS C:\Users\tomakabe\go\src\github.com\torumakabe\work> ls


    ディレクトリ: C:\Users\tomakabe\go\src\github.com\torumakabe\work


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2018/05/02     11:54        2049536 hello.exe
-a----       2018/05/02     11:10             91 hello.go


PS C:\Users\tomakabe\go\src\github.com\torumakabe\work> .\hello.exe
Hello Go on the new WSL

ここまでは従来のWindowsにおけるGo開発環境です。ではWSLに話を移しましょう。

そぞろ歩き その2(WSLでのGo開発)

WSLにつなぎます。ターミナルは任意ですが、わたしはVS Codeの統合ターミナルが好きです。コードを書きながら操作できるので。

GOPATHを確認します。空っぽです。WSLは既定でWindowsから環境変数PATHを受け取ります。PATHは特別扱いです。ですが、他の環境変数は、指定しないと渡されません。よってWindowsで設定していても、WSLから見るとGOPATHは空っぽです。

~ $ echo $GOPATH

$HOMEもきれいな状態です。

~ $ ls
~ $

ではGOPATHに指定したい、先ほどWindowsで確認したディレクトリへ移動します。ちなみにWindowsのCドライブはWSLで/mnt/c/に変換されます。先ほど確認したbin、srcが見えています。

~ $ cd /mnt/c/Users/tomakabe/go/
/mnt/c/Users/tomakabe/go $ ls
bin  src

ではここで実験。試しにパッケージをインポートしてみましょう。定番のgoimportsをインポートしてみます。わざとらしいですが、なんだか嫌な予感がします。

/mnt/c/Users/tomakabe/go $ go get -v golang.org/x/tools/cmd/goimports
Fetching https://golang.org/x/tools/cmd/goimports?go-get=1
Parsing meta tags from https://golang.org/x/tools/cmd/goimports?go-get=1 (status code 200)
get "golang.org/x/tools/cmd/goimports": found meta tag get.metaImport{Prefix:"golang.org/x/tools", VCS:"git", RepoRoot:"https://go.googlesource.com/tools"} at https://golang.org/x/tools/cmd/goimports?go-get=1
get "golang.org/x/tools/cmd/goimports": verifying non-authoritative meta tag
Fetching https://golang.org/x/tools?go-get=1
Parsing meta tags from https://golang.org/x/tools?go-get=1 (status code 200)
golang.org/x/tools (download)
created GOPATH=/home/tomakabe/go; see 'go help gopath'

嫌な予感は予定調和で的中します。GOPATHがいらっしゃらないので、/home/tomakabe/go とみなしてしまいました。先ほど確認した際、$HOMEはきれいな状態でした、が。新たにお作りになられたようです。

/mnt/c/Users/tomakabe/go $ ls ~/
go
/mnt/c/Users/tomakabe/go $ ls ~/go
bin  src

これではWSLとWindowsで、ソースもバイナリーも別々の管理になってしまいます。これはつらい。ああ、GOPATHを共有できればいいのに。

そぞろ歩き その3(解決編)

そこで登場するのが、WSLENVです。Windowsで作業します。Windowsの環境変数GOPATHを、環境変数WSLENVへスイッチとともに設定します。/pスイッチは、「この環境変数はパスを格納しているから、いい感じにして」という指定です。

PS C:\Users\tomakabe\go\src\github.com\torumakabe\work> setx WSLENV "$env:WSLENV`:GOPATH/p"

成功: 指定した値は保存されました。

いい感じって何よ。それは環境に合わせたパス表現の変換です。WSLで見てみましょう。WSLENVを読ませる必要があるため、VS Codeを再起動します。そして、ターミナルで確認します。

/mnt/c/Users/tomakabe/go $ echo $GOPATH
/mnt/c/Users/tomakabe/go

GOPATHが読めるようになりました。かつ、Windowsのパス表現であるC:\Users\tomakabe\goから、WSLの表現である/mnt/c/Users/tomakabe/goへと変換して渡しています。素晴らしい。これでGOPATHはひとつになり、ソースやバイナリー、パッケージの管理を統一できます。

ではWSLでサンプルコードを触ってみましょう。ソースのあるディレクトリへ移動します。ソースと先ほどビルドしたexeがあります。

/mnt/c/Users/tomakabe/go $ cd src/github.com/torumakabe/work/
/mnt/c/Users/tomakabe/go/src/github.com/torumakabe/work $ ls
hello.exe  hello.go

WSL上でビルドします。ELFバイナリー hello が作られました。

/mnt/c/Users/tomakabe/go/src/github.com/torumakabe/work $ go build hello.go
/mnt/c/Users/tomakabe/go/src/github.com/torumakabe/work $ ls
hello  hello.exe  hello.go
/mnt/c/Users/tomakabe/go/src/github.com/torumakabe/work $ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
/mnt/c/Users/tomakabe/go/src/github.com/torumakabe/work $ ./hello
Hello Go on the new WSL

まとめ

代表例としてGoの開発環境で説明しましたが、WSLENVは他の用途でも応用できるでしょう。スイッチの説明など、詳細は先ほど紹介した、こちらを。