Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16695] ファイルの概要

このコミットは、Go言語のVim用オートコンプリートプラグイン(misc/vim/autoload/go/complete.vim)における改善を目的としています。具体的には、複数のGOOS(オペレーティングシステム)とGOARCH(アーキテクチャ)の組み合わせに対応できるよう、パッケージ検索ロジックを修正しています。これにより、異なるOSやアーキテクチャ向けにビルドされたGoパッケージの補完が、Vimエディタ内でより正確に行われるようになります。

コミット

commit efced7c6e984f26b4c275b19ba61f2c2629d95ea
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Tue Jul 2 15:24:09 2013 +1000

    misc/vim: Allow multiple GOOS/GOARCH.
    
    R=golang-dev, dsymonds, dominik.honnef
    CC=golang-dev
    https://golang.org/cl/9293043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/efced7c6e984f26b4c275b19ba61f2c2629d95ea

元コミット内容

このコミットの元の内容は、Go言語のVimプラグインであるmisc/vim/autoload/go/complete.vimファイルに対する変更です。主な目的は、Goパッケージのオートコンプリート機能が、単一のGOOSおよびGOARCHの組み合わせに限定されず、複数の環境に対応できるようにすることです。

変更の要点は以下の通りです。

  1. GOPATHのパス区切り文字の動的な決定: Windows環境ではパス区切り文字が;であるのに対し、Unix系システムでは:であるため、これをs:goos(現在のOS)に基づいて動的に判断するように変更されました。
  2. root変数の展開方法の変更: 以前はexpand(dir . '/pkg/' . s:goos . '_' . s:goarch)が単一のパスを返すと仮定されていましたが、実際には複数のパスを返す可能性があるため、split関数を使って複数行に展開される可能性を考慮するように修正されました。これにより、globpathが複数のpkgディレクトリを正しく探索できるようになります。

これらの変更により、VimのGoオートコンプリート機能が、クロスコンパイル環境や異なるOS/アーキテクチャのGoパッケージをより適切に認識し、補完できるようになりました。

変更の背景

Go言語は、その強力なクロスコンパイル機能で知られています。開発者は、自身の開発環境とは異なるオペレーティングシステム(GOOS)やCPUアーキテクチャ(GOARCH)向けにバイナリをビルドすることができます。例えば、Linux上でWindows向けの実行ファイルをビルドしたり、ARMプロセッサ向けにビルドしたりすることが可能です。

Goのビルドシステムでは、コンパイルされたパッケージ(.aファイル)は通常、$GOROOT/pkg/<GOOS>_<GOARCH>$GOPATH/pkg/<GOOS>_<GOARCH>といったディレクトリに保存されます。VimのGoオートコンプリートプラグインは、これらのディレクトリを探索して利用可能なパッケージを特定し、補完候補として提示します。

しかし、このコミット以前のオートコンプリートロジックは、単一のGOOSGOARCHの組み合わせ(通常は現在のシステムのもの)しか考慮していませんでした。このため、開発者が複数のGOOS/GOARCH環境で作業している場合や、クロスコンパイルされたパッケージを参照したい場合に、Vimのオートコンプリートが正しく機能しないという問題がありました。例えば、GOOS=windows GOARCH=amd64でビルドしたパッケージが、GOOS=linux GOARCH=amd64で動作しているVimからは補完候補として認識されない、といった状況が発生していました。

このコミットは、この制限を解消し、VimのオートコンプリートがGoのクロスコンパイル機能をより適切にサポートできるようにするために導入されました。

前提知識の解説

Go言語の環境変数 GOOSGOARCH

Go言語のビルドシステムは、GOOSGOARCHという2つの環境変数によって、ターゲットとなるオペレーティングシステムとCPUアーキテクチャを決定します。

  • GOOS (Go Operating System): ビルドターゲットのオペレーティングシステムを指定します。例えば、linux, windows, darwin (macOS), freebsd, android, ios などがあります。
  • GOARCH (Go Architecture): ビルドターゲットのCPUアーキテクチャを指定します。例えば、amd64 (x86-64), 386 (x86), arm, arm64, ppc64, s390x などがあります。

これらの変数を設定することで、Goコンパイラは指定されたOSとアーキテクチャに最適化されたバイナリを生成します。例えば、GOOS=windows GOARCH=amd64 go buildと実行すると、Windows 64ビット環境で動作する実行ファイルが生成されます。

Goのパッケージは、これらのGOOSGOARCHの組み合わせごとに、$GOROOT/pkg/<GOOS>_<GOARCH>$GOPATH/pkg/<GOOS>_<GOARCH>といったディレクトリにキャッシュされます。例えば、Linux AMD64環境でビルドされた標準ライブラリのパッケージは$GOROOT/pkg/linux_amd64に、Windows AMD64環境でビルドされたものは$GOROOT/pkg/windows_amd64に格納されます。

Vimのオートコンプリート機能

Vimは、プログラミング言語の構文補完をサポートするための強力な機能を備えています。Go言語の場合、go#complete#Packageのような関数がVimスクリプトで実装されており、これがGoのパッケージ名や関数名などを補完候補として提示します。

この機能は通常、以下の手順で動作します。

  1. ユーザーがVimでGoコードを記述し、補完をトリガーする(例: .を入力する、特定のキーバインドを押す)。
  2. VimのGoプラグインが、現在のコンテキスト(カーソル位置、入力中の文字列など)に基づいて、補完候補を生成するための情報を収集する。
  3. プラグインは、GOROOTGOPATHで指定されたディレクトリ内のpkgサブディレクトリを探索し、コンパイル済みのGoパッケージ(.aファイル)やソースコードディレクトリを特定する。
  4. 特定されたパッケージから、補完候補となる名前(パッケージ名、関数名、変数名など)を抽出し、Vimに返す。
  5. Vimはこれらの候補をドロップダウンリストなどで表示し、ユーザーが選択できるようにする。

globpath関数とexpand関数

Vimスクリプトには、ファイルパスを操作するためのいくつかの組み込み関数があります。

  • globpath(path, expr): 指定されたpath内で、exprにマッチするファイルやディレクトリのリストを返します。exprにはワイルドカード(*, ?, **など)を使用できます。例えば、globpath('/usr/local/go/pkg/linux_amd64', '*')は、そのディレクトリ内のすべてのファイルとディレクトリを返します。
  • expand(expr): exprに含まれる特殊文字(例: ~、環境変数 $VAR)を展開し、完全なパスに変換します。また、expandは、パスが複数行にわたる場合(例えば、globpathの結果をexpandに渡した場合など)に、それらを改行区切りで返すことがあります。

このコミットでは、特にexpand関数が複数行の結果を返す可能性が考慮されていなかった点が問題となっていました。

技術的詳細

このコミットの技術的な核心は、VimのGoオートコンプリートプラグインがGoのパッケージを探索する際に、GOOSGOARCHの組み合わせによって生成される複数のpkgディレクトリを適切に処理できるようにすることです。

以前のコードでは、root変数を以下のように定義していました。

let root = expand(dir . '/pkg/' . s:goos . '_' . s:goarch)

ここで、s:gooss:goarchは、Vimが起動している現在の環境のOSとアーキテクチャを表す変数です。この行は、例えば/home/user/go/pkg/linux_amd64のような単一のパスを生成することを意図していました。しかし、Goのビルドシステムや環境設定によっては、go env GOROOTgo env GOPATHが複数のパスを返すことがあり、その結果、expand関数が複数行の文字列を返す可能性がありました。

例えば、GOROOT/usr/local/go:/opt/goのように設定されている場合、expand(dir . '/pkg/' . s:goos . '_' . s:goarch)は、/usr/local/go/pkg/linux_amd64\n/opt/go/pkg/linux_amd64のような複数行の文字列を返す可能性があります。

以前のコードでは、このroot変数が単一のパスであると仮定し、その後のglobpath関数がその単一のパス内でパッケージを探索していました。このため、expandが複数行を返した場合、globpathは最初の行しか処理せず、残りのパスにあるパッケージを見落としていました。

このコミットでは、この問題を解決するために、root変数の展開結果をsplit関数で改行文字("\n")によって分割し、それぞれのパスを個別に処理するように変更しました。

let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
for r in root
  for i in split(globpath(r, a:ArgLead.'*'), "\n")
    " ... (既存のロジック)
  endfor
endfor

これにより、rootが複数のパスを含んでいたとしても、それぞれのパス(r)に対してglobpathが実行され、すべての関連するpkgディレクトリが適切に探索されるようになりました。

また、GOPATHのパス区切り文字がOSによって異なる(Unix系は:、Windowsは;)という点も考慮され、pathsep変数を導入して動的に区切り文字を決定するように修正されました。これにより、Windows環境でもGOPATHが正しく解析され、複数のワークスペースがオートコンプリートの対象に含まれるようになりました。

コアとなるコードの変更箇所

変更はmisc/vim/autoload/go/complete.vimファイルに集中しています。

--- a/misc/vim/autoload/go/complete.vim
+++ b/misc/vim/autoload/go/complete.vim
@@ -32,39 +32,46 @@ function! go#complete#Package(ArgLead, CmdLine, CursorPos)
   let dirs = []
 
   if executable('go')
-      let goroot = substitute(system('go env GOROOT'), '\n', '', 'g')
-      if v:shell_error
-          echo '\'go env GOROOT\' failed'
-      endif
+    let goroot = substitute(system('go env GOROOT'), '\n', '', 'g')
+    if v:shell_error
+      echomsg '\'go env GOROOT\' failed'
+    endif
   else
-      let goroot = $GOROOT
+    let goroot = $GOROOT
   endif
 
   if len(goroot) != 0 && isdirectory(goroot)
-    let dirs += [ goroot ]
+    let dirs += [goroot]
   endif
 
-  let workspaces = split($GOPATH, ':')
+  let pathsep = ':'
+  if s:goos == 'windows'
+    let pathsep = ';'
+  endif
+  let workspaces = split($GOPATH, pathsep)
   if workspaces != []
-      let dirs += workspaces
+    let dirs += workspaces
   endif
 
   if len(dirs) == 0
-      " should not happen
-      return []
+    " should not happen
+    return []
   endif
 
   let ret = {}
   for dir in dirs
-    let root = expand(dir . '/pkg/' . s:goos . '_' . s:goarch)
-    for i in split(globpath(root, a:ArgLead.'*'), "\n")
-      if isdirectory(i)
-        let i .= '/'
-      elseif i !~ '\.a$'
-        continue
-      endif
-      let i = substitute(substitute(i[len(root)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
-      let ret[i] = i
+    " this may expand to multiple lines
+    let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
+    for r in root
+      for i in split(globpath(r, a:ArgLead.'*'), "\n")
+        if isdirectory(i)
+          let i .= '/'
+        elseif i !~ '\.a$'
+          continue
+        endif
+        let i = substitute(substitute(i[len(r)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
+        let ret[i] = i
+      endfor
     endfor
   endfor
   return sort(keys(ret))

コアとなるコードの解説

このコミットにおける主要な変更点は2つあります。

  1. GOPATHのパス区切り文字の動的な設定:

    let pathsep = ':'
    if s:goos == 'windows'
      let pathsep = ';'
    endif
    let workspaces = split($GOPATH, pathsep)
    

    この部分では、GOPATH環境変数を分割する際の区切り文字を、現在のオペレーティングシステム(s:goos)に基づいて動的に設定しています。Unix系システムでは:が、Windowsでは;がパスの区切り文字として使用されるため、これによりWindows環境でもGOPATHが正しく解析され、複数のワークスペースディレクトリがworkspacesリストに追加されるようになります。

  2. rootパスの複数行展開への対応:

    " this may expand to multiple lines
    let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
    for r in root
      for i in split(globpath(r, a:ArgLead.'*'), "\n")
        " ... (既存のパッケージ探索ロジック)
        let i = substitute(substitute(i[len(r)+1:], '[\\]', '/', 'g'), '\.a$', '', 'g')
        let ret[i] = i
      endfor
    endfor
    

    この変更が最も重要です。

    • expand(dir . '/pkg/' . s:goos . '_' . s:goarch): この式は、GOROOTGOPATHの各エントリ(dir)に対して、対応するpkgディレクトリのパスを生成します。前述の通り、expand関数は、入力によっては複数行のパスを返す可能性があります。
    • split(..., "\n"): expandの戻り値を改行文字"\n"で分割することで、root変数が単一の文字列ではなく、複数のパス文字列のリスト(VimScriptではリスト)として扱われるようになります。
    • for r in root: 新たに導入されたこのループは、rootリスト内の各パスrを個別に処理します。これにより、expandが返したすべてのpkgディレクトリが確実に探索対象となります。
    • globpath(r, a:ArgLead.'*'): 各r(個々のpkgディレクトリパス)に対してglobpathが実行され、そのディレクトリ内のGoパッケージ(.aファイル)やサブディレクトリが検索されます。
    • i[len(r)+1:]: パッケージ名の抽出部分も、len(root)からlen(r)に変更されています。これは、iからpkgディレクトリのパス部分を削除して、純粋なパッケージパス(例: net/http)を取得するためです。rは単一のpkgディレクトリパスであるため、この変更は正しい動作を保証します。

これらの変更により、VimのGoオートコンプリートは、Goのクロスコンパイル機能によって生成された複数のpkgディレクトリを適切にスキャンし、より包括的な補完候補を提供できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のGOOSGOARCHに関する公式ドキュメントやチュートリアル
  • VimScriptのexpandglobpathsplit関数に関するVimヘルプドキュメント
  • Go言語のVimプラグインのソースコード(misc/vimディレクトリ)
  • Go言語のGOPATHに関する公式ドキュメント