[インデックス 13616] ファイルの概要
このコミットは、Go言語のドキュメンテーションツールであるgodoc
の挙動を修正するものです。具体的には、単一のディレクトリ内に複数のGoパッケージが存在する場合に、godoc
がエラーを適切に報告するように変更されています。これまでは、このような状況で不適切な情報が表示される可能性がありましたが、この変更により、明確なエラーメッセージがユーザーに提示されるようになります。
コミット
commit f597fa67c16dbce225ceb8482acd92a5c474fc19
Author: Robert Griesemer <gri@golang.org>
Date: Thu Aug 9 16:10:46 2012 -0700
godoc: report error for directories with multiple packages
Fixes #3922.
R=rsc, adg
CC=golang-dev
https://golang.org/cl/6453094
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f597fa67c16dbce225ceb8482acd92a5c474fc19
元コミット内容
このコミットの元の内容は、godoc
がディレクトリ内に複数のパッケージが存在する場合にエラーを報告するように修正することです。これは、GoのIssue #3922を解決するためのものです。
変更の背景
Go言語のパッケージ管理において、通常、一つのディレクトリには一つのGoパッケージが含まれることが推奨されています。しかし、何らかの理由で一つのディレクトリ内に複数のpackage
宣言を持つGoファイルが存在する場合があります。従来のgodoc
は、このような状況に遭遇した際に、どのパッケージのドキュメントを表示すべきかを判断しようと試み、場合によっては誤った情報や不完全な情報を表示してしまう可能性がありました。
この挙動はユーザーにとって混乱を招くものであり、godoc
が提供する情報の正確性を損なうものでした。そのため、GoのIssue #3922として「godoc: report error for directories with multiple packages」(godoc: 複数のパッケージを含むディレクトリに対してエラーを報告する)が提起されました。このコミットは、その問題に対する直接的な解決策として、曖昧な状況で推測して表示するのではなく、明確なエラーを報告するようにgodoc
の動作を変更することを目的としています。これにより、ユーザーはディレクトリ構造の問題を認識し、修正できるようになります。
前提知識の解説
- Goパッケージ: Go言語では、関連するソースファイルの集まりを「パッケージ」と呼びます。各Goファイルは
package
キーワードで始まるパッケージ宣言を持ち、通常、同じディレクトリ内のすべてのGoファイルは同じパッケージに属します。 godoc
:godoc
はGo言語の公式ドキュメンテーションツールです。Goのソースコードからコメントや宣言を解析し、HTML形式やプレーンテキスト形式でドキュメントを生成・表示します。開発者がコードのドキュメントを簡単に参照できるようにするために非常に重要なツールです。go/build
パッケージ: Goの標準ライブラリの一部であり、Goのビルドプロセスに関連する情報(Goファイルの解析、パッケージのインポートパスの解決、ビルドタグの処理など)を提供します。godoc
は、このパッケージを利用してGoソースファイルを解析し、パッケージ情報を取得します。go/ast
パッケージ: Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として表現するためのデータ構造と関数を提供します。godoc
は、このパッケージを使用してGoソースファイルを解析し、コードの構造を理解します。token.FileSet
:go/parser
パッケージでGoソースファイルを解析する際に使用されるファイルセットです。ソースコード内の位置情報を管理します。ast.Package
:go/ast
パッケージにおける、Goパッケージを表す構造体です。パッケージ名や、そのパッケージに属するファイルごとのAST情報を含みます。build.Context
:go/build
パッケージにおけるビルドコンテキストを表す構造体です。Goのビルド環境(GOOS, GOARCH, GOPATHなど)に関する情報を含み、ファイルの読み込みやディレクトリのスキャン方法をカスタマイズできます。
技術的詳細
このコミットの主要な変更点は、src/cmd/godoc/godoc.go
内のdocServer.getPageInfo
関数のロジックにあります。
-
PageInfo
構造体の変更:PageInfo
構造体からPList []string
フィールドが削除されました。これは、ディレクトリ内に複数のパッケージが存在する場合に、それらのパッケージ名をリストアップして表示するという従来の挙動が廃止されたためです。
-
getPageInfo
関数の引数変更:getPageInfo
関数のシグネチャが変更され、pkgname string
引数が削除されました。これは、特定のパッケージ名を指定してそのパッケージの情報を取得するというアプローチから、ディレクトリ内のパッケージの整合性をチェックするアプローチに変わったことを示しています。
-
複数パッケージ検出時のエラー報告:
- 最も重要な変更は、
parseDir
関数(ディレクトリ内のGoファイルを解析し、パッケージのASTを返す)が複数のast.Package
を返した場合の処理です。 - 変更前は、複数のパッケージが見つかった場合、
godoc
は「最適な」パッケージを選択しようと試み、他のパッケージのリスト(PList
)も生成していました。この選択ロジックは、パッケージ名がディレクトリ名と一致するか、main
パッケージでないか、といった基準に基づいていました。 - 変更後は、
len(pkgs) > 1
(複数のパッケージが見つかった場合)の条件で、直ちにエラーを生成して返します。エラーメッセージは、どのディレクトリが複数のパッケージを含んでいるか、そしてそれらのパッケージ名が何であるかを明確に示します。
ここでreturn PageInfo{ Dirname: abspath, Err: fmt.Errorf("%s contains more than one package: %s", abspath, buf.Bytes()), }
buf.Bytes()
は、見つかった複数のパッケージ名をカンマ区切りで連結した文字列です。
- 最も重要な変更は、
-
HTML/テキストテンプレートの変更:
lib/godoc/package.html
とlib/godoc/package.txt
から、PList
に関連する表示ロジック("Other packages"セクション)が削除されました。これは、godoc
が複数のパッケージをリストアップする機能を廃止したため、それに対応する表示も不要になったためです。
これらの変更により、godoc
はディレクトリ内のパッケージの曖昧さを許容せず、明確なエラーとしてユーザーに通知するようになりました。
コアとなるコードの変更箇所
src/cmd/godoc/godoc.go
--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -810,7 +810,6 @@ func remoteSearchURL(query string, html bool) string {
type PageInfo struct {
Dirname string // directory containing the package
- PList []string // list of package names found
FSet *token.FileSet // corresponding file set
PAst *ast.File // nil if no single AST with package exports
PDoc *doc.Package // nil if no single package documentation
@@ -876,27 +875,24 @@ func packageExports(fset *token.FileSet, pkg *ast.Package) {
// directories, PageInfo.Dirs is nil. If a directory read error occurred,\n // PageInfo.Err is set to the respective error but the error is not logged.\n //
-func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo {
+func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) PageInfo {
var pkgFiles []string
- // If we're showing the default package, restrict to the ones
+ // Restrict to the package files
// that would be used when building the package on this
// system. This makes sure that if there are separate
// implementations for, say, Windows vs Unix, we don't
// jumble them all together.
- if pkgname == "" {
- // Note: Uses current binary's GOOS/GOARCH.
- // To use different pair, such as if we allowed the user
- // to choose, set ctxt.GOOS and ctxt.GOARCH before
- // calling ctxt.ScanDir.
- ctxt := build.Default
- ctxt.IsAbsPath = pathpkg.IsAbs
- ctxt.ReadDir = fsReadDir
- ctxt.OpenFile = fsOpenFile
- dir, err := ctxt.ImportDir(abspath, 0)
- if err == nil {
- pkgFiles = append(dir.GoFiles, dir.CgoFiles...)
- }
+ // Note: Uses current binary's GOOS/GOARCH.
+ // To use different pair, such as if we allowed the user
+ // to choose, set ctxt.GOOS and ctxt.GOARCH before
+ // calling ctxt.ScanDir.
+ ctxt := build.Default
+ ctxt.IsAbsPath = pathpkg.IsAbs
+ ctxt.ReadDir = fsReadDir
+ ctxt.OpenFile = fsOpenFile
+ if dir, err := ctxt.ImportDir(abspath, 0); err == nil {
+ pkgFiles = append(dir.GoFiles, dir.CgoFiles...)
}
// filter function to select the desired .go files
@@ -917,15 +913,12 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM
// get package ASTs
fset := token.NewFileSet()\n pkgs, err := parseDir(fset, abspath, filter)\n- if err != nil && pkgs == nil {\n- // only report directory read errors, ignore parse errors\n- // (may be able to extract partial package information)\n+ if err != nil {\n return PageInfo{Dirname: abspath, Err: err}\n }\n
// select package
var pkg *ast.Package // selected package
- var plist []string // list of other package (names), if any
if len(pkgs) == 1 {\n // Exactly one package - select it.\n for _, p := range pkgs {\n@@ -933,49 +926,18 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM
}\n
} else if len(pkgs) > 1 {\n- // Multiple packages - select the best matching package: The
- // 1st choice is the package with pkgname, the 2nd choice is
- // the package with dirname, and the 3rd choice is a package
- // that is not called "main" if there is exactly one such
- // package. Otherwise, don't select a package.
- dirpath, dirname := pathpkg.Split(abspath)
-
- // If the dirname is "go" we might be in a sub-directory for
- // .go files - use the outer directory name instead for better
- // results.
- if dirname == "go" {
- _, dirname = pathpkg.Split(pathpkg.Clean(dirpath))
- }
-
- var choice3 *ast.Package
- loop:
+ // More than one package - report an error.
+ var buf bytes.Buffer
for _, p := range pkgs {\n- switch {\n- case p.Name == pkgname:\n- pkg = p\n- break loop // 1st choice; we are done
- case p.Name == dirname:\n- pkg = p // 2nd choice
- case p.Name != "main":\n- choice3 = p
+ if buf.Len() > 0 {
+ fmt.Fprintf(&buf, ", ")
}\n+ fmt.Fprintf(&buf, p.Name)
}\n- if pkg == nil && len(pkgs) == 2 {\n- pkg = choice3
- }\n-
- // Compute the list of other packages
- // (excluding the selected package, if any).
- // (excluding the selected package, if any).
- plist = make([]string, len(pkgs))
- i := 0
- for name := range pkgs {\n- if pkg == nil || name != pkg.Name {\n- plist[i] = name
- i++
- }\n+ return PageInfo{\n+ Dirname: abspath,\n+ Err: fmt.Errorf("%s contains more than one package: %s", abspath, buf.Bytes()),
+ }
- plist = plist[0:i]
- sort.Strings(plist)
}\n
// get examples from *_test.go files
@@ -1041,7 +1003,6 @@ func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoM
return PageInfo{\n Dirname: abspath,\n- PList: plist,\n FSet: fset,\n PAst: past,\n PDoc: pdoc,\n@@ -1065,7 +1026,7 @@ func (h *docServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n if relpath == builtinPkgPath {\n mode = noFiltering\n }\n- info := h.getPageInfo(abspath, relpath, r.FormValue("p"), mode)\n+ info := h.getPageInfo(abspath, relpath, mode)\n if info.Err != nil {\n log.Print(info.Err)\n serveError(w, r, relpath, info.Err)\n```
### `src/cmd/godoc/main.go`
`main.go`では、`pkgHandler.getPageInfo`と`cmdHandler.getPageInfo`の呼び出しから、`pkgname`引数(空文字列`""`)が削除されています。これは、`getPageInfo`関数のシグネチャ変更に伴うものです。
```diff
--- a/src/cmd/godoc/main.go
+++ b/src/cmd/godoc/main.go
@@ -373,13 +373,11 @@ func main() {
}\n mode |= showSource\n }\n- // TODO(gri): Provide a mechanism (flag?) to select a package
- // if there are multiple packages in a directory.
// first, try as package unless forced as command
var info PageInfo\n if !forceCmd {\n- info = pkgHandler.getPageInfo(abspath, relpath, "", mode)\n+ info = pkgHandler.getPageInfo(abspath, relpath, mode)\n }\n
// second, try as command unless the path is absolute
var cinfo PageInfo\n if !filepath.IsAbs(path) {\n abspath = pathpkg.Join(cmdHandler.fsRoot, path)\n- cinfo = cmdHandler.getPageInfo(abspath, relpath, "", mode)\n+cinfo = cmdHandler.getPageInfo(abspath, relpath, mode)\n }\n
// determine what to use
lib/godoc/package.html
PList
に関連するHTML要素が削除されています。
--- a/lib/godoc/package.html
+++ b/lib/godoc/package.html
@@ -21,9 +21,6 @@
{{if $.Examples}}
<dd><a href="#pkg-examples">Examples</a></dd>
{{end}}
- {{if $.PList}}
- <dd><a href="#pkg-other-packages">Other packages</a></dd>
- {{end}}
{{if $.Dirs}}
<dd><a href="#pkg-subdirectories">Subdirectories</a></dd>
{{end}}
@@ -167,16 +164,6 @@
<pre>{{node_html . $.FSet}}</pre>
{{end}}
-{{with .PList}}
- <h2 id="pkg-other-packages">Other packages</h2>
- <p>
- {{/* PList entries are strings - no need for FSet */}}
- {{range .}}
- <a href="?p={{urlquery .}}">{{html .}}</a><br />
- {{end}}
- </p>
-{{end}}
-
{{with .Dirs}}
{{/* DirList entries are numbers and strings - no need for FSet */}}
{{if $.PDoc}}
lib/godoc/package.txt
PList
に関連するテキスト要素が削除されています。
--- a/lib/godoc/package.txt
+++ b/lib/godoc/package.txt
@@ -65,15 +65,6 @@ BUGS
---------------------------------------
-*/}}{{with .PList}}\n-OTHER PACKAGES\n-\n-{{range .}}\n-{{.}}{{end}}\n-{{end}}{{/*\n-\n----------------------------------------\n-\n */}}{{with .Dirs}}\n SUBDIRECTORIES\n {{if $.DirFlat}}{{range .List}}{{if .HasPkg}}\n```
## コアとなるコードの解説
このコミットの核心は、`src/cmd/godoc/godoc.go`内の`docServer.getPageInfo`関数における、ディレクトリ内のGoパッケージの解析と処理ロジックの変更です。
変更前は、`getPageInfo`関数は`parseDir`関数によってディレクトリ内のすべてのGoファイルを解析し、見つかったすべてのパッケージのAST(`pkgs`マップ)を取得していました。もし`pkgs`の要素数が1より大きい場合(つまり、複数のパッケージが見つかった場合)、`godoc`は以下の複雑なロジックで「最適な」パッケージを選択しようと試みていました。
1. リクエストされたパッケージ名(`pkgname`引数で渡される)と一致するパッケージを最優先。
2. ディレクトリ名と一致するパッケージ。
3. `main`パッケージではない、唯一のパッケージ。
この選択ロジックは、ユーザーが意図しないパッケージのドキュメントが表示されたり、混乱を招く可能性がありました。また、選択されなかった他のパッケージは`PList`として保持され、HTML/テキスト出力で「Other packages」として表示されていました。
このコミットでは、この複雑な選択ロジックが完全に削除されました。代わりに、`len(pkgs) > 1`という条件が満たされた場合、つまりディレクトリ内に複数のパッケージが検出された場合、`godoc`は直ちに`fmt.Errorf`を使用してエラーを生成し、そのエラーを`PageInfo.Err`フィールドに設定して返します。エラーメッセージには、問題のディレクトリのパスと、検出されたすべてのパッケージ名が明示的に含まれます。
これにより、`godoc`は曖昧な状況で推測するのではなく、明確なエラーを報告するようになり、ユーザーはディレクトリ構造の問題を迅速に特定し、修正できるようになります。これは、Goの「明示的であること」という設計哲学にも合致する変更と言えます。
また、`PageInfo`構造体から`PList`フィールドが削除され、関連するHTML/テキストテンプレートの表示ロジックも削除されたことで、コードベースが簡素化され、`godoc`の挙動がより予測可能になりました。
## 関連リンク
* Go Issue #3922: [https://github.com/golang/go/issues/3922](https://github.com/golang/go/issues/3922) (このコミットが修正したIssue)
* Go CL 6453094: [https://golang.org/cl/6453094](https://golang.org/cl/6453094) (このコミットに対応するGoの変更リスト)
## 参考にした情報源リンク
* Go Issue Tracker: [https://github.com/golang/go/issues](https://github.com/golang/go/issues)
* Go Documentation: [https://pkg.go.dev/](https://pkg.go.dev/)
* `go/build`パッケージ: [https://pkg.go.dev/go/build](https://pkg.go.dev/go/build)
* `go/ast`パッケージ: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
* `go/token`パッケージ: [https://pkg.go.dev/go/token](https://pkg.go.dev/go/token)
* `godoc`コマンドの公式ドキュメント (Goのバージョンによって内容が異なる場合があります): `go doc cmd/godoc`