[インデックス 15314] ファイルの概要
コミット
commit 3ee87d02b063e368259486d83e4ea391538f84c2
Author: Robert Griesemer <gri@golang.org>
Date: Tue Feb 19 11:19:58 2013 -0800
cmd/godoc: use go/build to determine package and example files
Also:
- faster code for example extraction
- simplify handling of command documentation:
all "main" packages are treated as commands
- various minor cleanups along the way
For commands written in Go, any doc.go file containing
documentation must now be part of package main (rather
then package documentation), otherwise the documentation
won't show up in godoc (it will still build, though).
For commands written in C, documentation may still be
in doc.go files defining package documentation, but the
recommended way is to explicitly ignore those files with
a +build ignore constraint to define package main.
Fixes #4806.
R=adg, rsc, dave, bradfitz
CC=golang-dev
https://golang.org/cl/7333046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ee87d02b063e368259486d83e4ea391538f84c2
元コミット内容
cmd/godoc: go/build を使用してパッケージファイルとサンプルファイルを決定するように変更。
さらに以下の変更が含まれる:
- サンプル抽出のためのコードを高速化。
- コマンドドキュメントの扱いを簡素化: すべての "main" パッケージがコマンドとして扱われる。
- その他、様々な小規模なクリーンアップ。
Goで書かれたコマンドの場合、ドキュメントを含む doc.go ファイルは、package documentation ではなく package main の一部である必要がある。そうしないと、godoc にドキュメントが表示されなくなる(ビルドは可能)。
Cで書かれたコマンドの場合、ドキュメントは引き続きパッケージドキュメントを定義する doc.go ファイルに含めることができるが、推奨される方法は、+build ignore 制約を使用してこれらのファイルを明示的に無視し、package main を定義することである。
Issue #4806 を修正。
変更の背景
このコミットは、Goのドキュメントツールである godoc の動作改善を目的としています。特に、コマンドのドキュメント表示に関する問題(Issue #4806)を解決するために行われました。
従来の godoc は、コマンドのドキュメントを package documentation という特別なパッケージ名を持つ doc.go ファイルから抽出していました。しかし、これはGoのビルドシステムやパッケージの概念と必ずしも一致せず、混乱を招く可能性がありました。
この変更の主な動機は以下の点に集約されます。
-
go/buildパッケージの活用: Go 1.0以降、go/buildパッケージはGoのソースファイルをビルドタグや環境変数に基づいてフィルタリングし、パッケージを特定するための標準的な方法を提供しています。godocがこのパッケージを利用することで、Goのビルドシステムが認識するのと同じ方法でパッケージファイルやサンプルファイルを正確に特定できるようになります。これにより、godocの動作がより堅牢になり、Goのツールチェーン全体との整合性が向上します。 -
コマンドドキュメントの簡素化: 以前は、コマンド(実行可能ファイル)のドキュメントは
package documentationという特殊なパッケージ名を持つdoc.goファイルに記述されていました。しかし、Goの実行可能ファイルは常にpackage mainとして定義されます。この不一致が、godocでコマンドのドキュメントが正しく表示されない原因となることがありました。このコミットでは、すべてのpackage mainがコマンドとして扱われるようにすることで、このプロセスを簡素化し、より直感的な動作を実現しています。 -
C言語で書かれたコマンドのドキュメントの扱い: Goのツールチェーンには、C言語で書かれたコマンド(例:
5a,6cなど、Plan 9由来のツール)も含まれています。これらのコマンドのドキュメントもdoc.goファイルに記述されることがありますが、これらはGoのソースファイルではないため、go/buildの通常のルールでは処理されません。このコミットでは、これらのファイルに対して+build ignoreビルド制約を使用することを推奨し、godocがそれらを適切に無視しつつ、package mainとしてドキュメントを認識できるようにする新しいガイドラインを導入しています。 -
サンプルコード抽出の高速化:
godocはパッケージのサンプルコード(Example関数)も表示します。このコミットでは、サンプルコードの抽出ロジックを最適化し、パフォーマンスを向上させています。
これらの変更により、godoc はより正確に、より効率的に、そしてよりGoの慣習に沿った形でドキュメントを生成できるようになります。
前提知識の解説
このコミットの理解には、以下のGo言語および関連ツールの概念に関する知識が不可欠です。
-
godocツール:- Go言語の標準ドキュメント生成ツールです。ソースコード内のコメント(特にパッケージ、関数、型、変数などの宣言に付随するコメント)を解析し、HTML形式やプレーンテキスト形式でドキュメントを生成します。
- 開発者がコードを理解しやすくするために、コードとドキュメントを密接に連携させることを目的としています。
godocは、Goの標準ライブラリだけでなく、ユーザーが作成したパッケージやコマンドのドキュメントも表示できます。
-
go/buildパッケージ:- Goの標準ライブラリの一部で、Goのソースコードをビルドするための情報を提供します。
- 特定のディレクトリ内のGoソースファイルを解析し、どのファイルが特定のビルド環境(OS、アーキテクチャ、ビルドタグなど)でビルドされるべきかを判断する機能を持っています。
go/build.Context構造体は、ビルド環境に関する情報(GOOS,GOARCHなど)を含み、ImportDirメソッドは指定されたディレクトリからパッケージ情報を抽出します。GoFiles,CgoFiles,IgnoredGoFiles,TestGoFiles,XTestGoFilesなどのフィールドを通じて、ビルド対象となる様々な種類のファイルリストを提供します。
-
package mainとコマンド:- Go言語において、
package mainは実行可能なプログラムのエントリポイントを定義するパッケージです。mainパッケージ内のmain関数がプログラムの実行開始点となります。 go buildコマンドでビルドされると、package mainは実行可能バイナリを生成します。このような実行可能バイナリは「コマンド」と呼ばれます。godocは、パッケージのドキュメントだけでなく、コマンドのドキュメントも表示する機能を持っています。
- Go言語において、
-
doc.goファイル:- Goの慣習として、パッケージ全体のドキュメントは、そのパッケージ内の任意のGoソースファイル(通常は
doc.goという名前のファイル)のパッケージ宣言の直前に記述されたコメントブロックに含めることができます。 - このコメントは、
godocによってパッケージの概要として表示されます。 - 以前は、コマンドのドキュメントのために
package documentationという特殊なパッケージ名が使われることがありましたが、このコミットでpackage mainに統一されます。
- Goの慣習として、パッケージ全体のドキュメントは、そのパッケージ内の任意のGoソースファイル(通常は
-
ビルド制約 (
+buildタグ):- Goのソースファイルには、ファイルの先頭に
+buildタグを記述することで、特定のビルド環境でのみそのファイルをコンパイルするように指示できます。 - 例:
// +build linux,amd64はLinuxかつAMD64アーキテクチャでのみコンパイルされます。 // +build ignoreは、そのファイルをGoのビルドプロセスから完全に除外するための特別な制約です。これは、Goのソースファイルではないが、リポジトリ内に存在する必要があるファイル(例: ドキュメントファイル、テストデータなど)によく使用されます。このコミットでは、C言語で書かれたコマンドのドキュメントファイルに対してこの制約の使用が推奨されています。
- Goのソースファイルには、ファイルの先頭に
-
go/astおよびgo/docパッケージ:go/ast(Abstract Syntax Tree) パッケージは、Goのソースコードを解析して抽象構文木を構築するための機能を提供します。godocはこれを利用してコード構造を理解します。go/docパッケージは、go/astによって生成されたASTから、Goのドキュメントコメントを抽出し、構造化されたドキュメントモデルを生成するための機能を提供します。doc.Package,doc.Exampleなどの型が定義されています。
これらの概念を理解することで、コミットがGoのドキュメント生成プロセスとビルドシステムにどのように影響を与え、改善しているかを深く把握することができます。
技術的詳細
このコミットの技術的な変更は、主に src/cmd/godoc/godoc.go、src/cmd/godoc/dirtrees.go、src/cmd/godoc/parser.go の3つのファイルに集中しており、godoc がパッケージ情報とサンプルコードをどのように取得・処理するかを根本的に変更しています。
-
go/buildパッケージの導入と利用:- 最も重要な変更は、
godocがパッケージのGoソースファイルを特定するために、独自のファイルフィルタリングロジックからgo/buildパッケージのbuild.Contextを使用するように移行した点です。 src/cmd/godoc/godoc.goのgetPageInfo関数内で、build.Defaultコンテキストが初期化され、ctxt.ReadDirとctxt.OpenFileにgodoc独自のファイルシステム (fs) をラップした関数が設定されます。これにより、go/buildがgodocの仮想ファイルシステムを通じてファイルを読み込めるようになります。ctxt.ImportDir(abspath, 0)を呼び出すことで、指定されたディレクトリ (abspath) からビルド可能なGoファイル(GoFiles,CgoFiles)と無視されるGoファイル(IgnoredGoFiles)を含むbuild.Package情報が取得されます。これにより、godocは現在のビルド環境(GOOS,GOARCHなど)に合わせた正確なファイルセットを自動的に選択できるようになります。
- 最も重要な変更は、
-
コマンドドキュメントの扱いの一元化:
- 以前は、
godocはfakePkgName = "documentation"という特別なパッケージ名を使用してコマンドのドキュメントを識別していました。このコミットでは、この概念が削除され、すべてのpackage mainがコマンドとして扱われるようになります。 lib/godoc/package.htmlおよびlib/godoc/package.txtのテンプレートで、$.IsPkgの代わりにnot $.IsMainが使用されるようになり、mainパッケージではない場合にのみ "PACKAGE" ヘッダーが表示されるようになります。PageInfo構造体にIsMain boolフィールドが追加され、getPageInfo関数内でpkgname == "main"の場合にtrueが設定されます。これにより、godocはmainパッケージをコマンドとして適切に表示できるようになります。- 多くの
src/cmd/*/doc.goファイルがpackage documentationからpackage mainに変更され、さらに// +build ignore制約が追加されています。これは、これらのファイルがGoのビルドプロセスではコンパイルされないが、godocがドキュメントとして読み込むべきファイルであることを示しています。特にC言語で書かれたコマンドのドキュメントに対してこのアプローチが推奨されます。
- 以前は、
-
サンプル抽出の高速化と簡素化:
src/cmd/godoc/godoc.goのparseExamples関数がcollectExamplesにリネームされ、そのロジックが大幅に簡素化されています。- 以前は、
parseExamplesはparseDirを呼び出してテストファイルをフィルタリングしていましたが、新しいcollectExamplesはgo/buildから取得したpkginfo.TestGoFilesとpkginfo.XTestGoFilesを直接使用してテストファイルを読み込みます。これにより、ファイルシステムのスキャンが効率化されます。 globalNames関数も簡素化され、map[string]boolを直接受け取るように変更され、declNamesの代わりにaddNamesが使用されるようになりました。これにより、グローバルな宣言名の収集がより効率的になります。
-
parser.goの変更:src/cmd/godoc/parser.goからosパッケージのインポートが削除されました。これは、ファイルシステム操作がgodocのfsインターフェースを通じて行われるため、直接osを使用する必要がなくなったことを示しています。parseFiles関数が変更され、abspathとlocalnamesのリストを受け取るようになりました。これにより、go/buildが提供するファイルリストを直接利用してファイルを解析できるようになります。parseDir関数が削除されました。これは、ディレクトリ内のファイルをフィルタリングして解析するロジックがgo/buildのImportDirに置き換えられたためです。
-
dirtrees.goの変更:treeBuilder.newDirTree関数内で、synopses配列のサイズが[4]stringから[3]stringに変更されました。これは、fakePkgNameの概念が削除されたことによるものです。- パッケージの優先順位付けロジックが調整され、
fakePkgNameに関連するケースが削除されました。
これらの変更は、godoc の内部構造をより現代的なGoのビルドシステムと整合させ、パフォーマンスと保守性を向上させることを目的としています。特に、go/build の採用は、godoc がGoのツールチェーンの他の部分とよりシームレスに連携するための重要なステップです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。
-
src/cmd/godoc/godoc.go:PageInfo構造体の変更:IsPkg boolが削除され、IsMain boolが追加されました。また、FSet,PDoc,PAst,ExamplesのフィールドがErrの後に移動し、ErrがPageInfoの最初のフィールドになりました。docServer構造体の変更:isPkg boolフィールドが削除されました。getPageInfo関数の大幅な書き換え:go/build.Defaultコンテキストを使用して、ctxt.ImportDirを呼び出し、パッケージのGoファイル、Cgoファイル、無視されるGoファイル、テストGoファイル、XTestGoファイルを取得するようになりました。- 以前のファイルフィルタリングロジック(
filter関数)が削除されました。 parseExamplesがcollectExamplesに置き換えられ、go/buildから取得したテストファイルリストを直接使用するようになりました。IsMainフィールドがpkgname == "main"に基づいて設定されるようになりました。
ServeHTTP関数内のタイトル生成ロジックの変更:IsPkgのチェックがIsMainのチェックに置き換えられ、コマンドのタイトル生成が簡素化されました。fakePkgFileおよびfakePkgName定数の削除。inList関数の削除。declNames関数がaddNamesに変更され、map[string]boolを直接操作するようになりました。globalNames関数がpkgs map[string]*ast.Packageからpkg *ast.Packageを受け取るように変更されました。
-
src/cmd/godoc/dirtrees.go:treeBuilder.newDirTree関数内のsynopses配列のサイズが[4]stringから[3]stringに変更されました。fakePkgNameに関連するパッケージの優先順位付けロジックが削除されました。
-
src/cmd/godoc/parser.go:osパッケージのインポートが削除されました。parseFiles関数のシグネチャと実装が変更され、abspathとlocalnamesのリストを受け取るようになりました。parseDir関数が完全に削除されました。
-
lib/godoc/package.htmlおよびlib/godoc/package.txt:{{if $.IsPkg}}が{{if not $.IsMain}}に変更され、mainパッケージではない場合にのみパッケージ情報が表示されるようになりました。
-
src/cmd/*/doc.goファイル群:- 多くのコマンドの
doc.goファイルで、package documentationがpackage mainに変更され、さらに// +build ignoreが追加されました。これは、これらのファイルがGoのビルドプロセスでは無視されるが、godocがドキュメントとして読み込むべきファイルであることを示しています。
- 多くのコマンドの
これらの変更は、godoc の内部ロジックを go/build パッケージと統合し、コマンドドキュメントの扱いを簡素化し、全体的なパフォーマンスと正確性を向上させるためのものです。
コアとなるコードの解説
src/cmd/godoc/godoc.go の変更点
このファイルは godoc の主要なロジックを含んでおり、特に getPageInfo 関数が大幅に書き換えられています。
PageInfo 構造体の変更
type PageInfo struct {
Dirname string // directory containing the package
Err error // error or nil
// package info
FSet *token.FileSet // nil if no package documentation
PDoc *doc.Package // nil if no package documentation
Examples []*doc.Example // nil if no example code
PAst *ast.File // nil if no AST with package exports
IsMain bool // true for package main
// directory info
Dirs *DirList // nil if no directory information
DirTime time.Time // directory time stamp
DirFlat bool // if set, show directory in a flat (non-indented) manner
}
IsPkg boolフィールドが削除され、代わりにIsMain boolが追加されました。これは、godocが「実際のパッケージ」と「コマンド」を区別するロジックを、package mainであるかどうかで判断するように変更されたことを示しています。- フィールドの順序が変更され、エラー情報が先頭に来るようになりました。
docServer 構造体の変更
type docServer struct {
pattern string // url pattern; e.g. "/pkg/"
fsRoot string // file system root to which the pattern is mapped
// isPkg bool // true if this handler serves real package documentation (as opposed to command documentation) - REMOVED
}
isPkgフィールドが削除されました。これは、docServerがパッケージとコマンドのどちらを扱うかという区別を内部的に持つ必要がなくなったことを意味します。この区別は、PageInfo.IsMainを通じて行われるようになりました。
getPageInfo 関数の主要な変更
この関数は、特定のパスのパッケージ情報を取得する中心的な役割を担っています。
func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) (info PageInfo) {
info.Dirname = abspath
// go/build を使用してパッケージファイルを特定
ctxt := build.Default
ctxt.IsAbsPath = pathpkg.IsAbs
ctxt.ReadDir = fsReadDir
ctxt.OpenFile = fsOpenFile
pkginfo, err := ctxt.ImportDir(abspath, 0)
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
info.Err = err
return
}
// パッケージファイルを収集
pkgname := pkginfo.Name
pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
if len(pkgfiles) == 0 {
// C言語で書かれたコマンドの場合、.go ファイルがないことがある。
// その場合、ドキュメントは無視されたファイルにある可能性がある。
pkgname = "main" // pkginfo.Name が空の場合、package main と仮定
pkgfiles = pkginfo.IgnoredGoFiles
}
// パッケージ情報(AST、Doc、Examples)の取得
if len(pkgfiles) > 0 {
fset := token.NewFileSet()
files, err := parseFiles(fset, abspath, pkgfiles) // 新しい parseFiles を使用
if err != nil {
info.Err = err
return
}
pkg := &ast.Package{Name: pkgname, Files: files}
info.FSet = fset
if mode&showSource == 0 {
// ドキュメントを抽出
var m doc.Mode
if mode&showUnexported != 0 {
m |= doc.PreserveAST
}
if mode&allMethods != 0 {
m |= doc.AllMethods
}
info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m)
// サンプルを収集
testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
files, err = parseFiles(fset, abspath, testfiles) // 新しい parseFiles を使用
if err != nil {
log.Println("parsing examples:", err)
}
info.Examples = collectExamples(pkg, files) // 新しい collectExamples を使用
} else {
// ソースコードを表示
if mode&noFiltering == 0 {
packageExports(fset, pkg)
}
info.PAst = ast.MergePackageFiles(pkg, 0)
}
info.IsMain = pkgname == "main" // IsMain を設定
}
// ディレクトリ情報の取得 (変更なし)
// ...
return
}
go/buildの統合: 以前は、godocは独自のfilter関数とparseDirを使用してGoファイルを特定していました。このコミットでは、build.Default.ImportDirを使用することで、Goのビルドシステムが認識するのと同じ方法でGoファイルを特定できるようになりました。これにより、ビルドタグ(+build)や環境変数(GOOS,GOARCH)に基づくファイルのフィルタリングが自動的に行われます。- コマンドドキュメントの処理:
pkginfo.GoFilesが空の場合(C言語で書かれたコマンドなど)、pkginfo.IgnoredGoFilesをチェックし、pkgnameを"main"と仮定するロジックが追加されました。これにより、+build ignoreでマークされたdoc.goファイルがコマンドのドキュメントとして適切に扱われます。 - サンプル抽出の改善:
parseExamplesがcollectExamplesに置き換えられ、go/buildから取得したTestGoFilesとXTestGoFilesを直接使用するようになりました。これにより、テストファイルの特定と解析がより効率的になります。 IsMainの設定:pkgname == "main"に基づいてinfo.IsMainが設定されるようになり、godocがmainパッケージをコマンドとして適切に識別できるようになりました。
globalNames および collectExamples の変更
// globalNames returns a set of the names declared by all package-level
// declarations. Method names are returned in the form ReceiverTypeName_Method.
func globalNames(pkg *ast.Package) map[string]bool {
names := make(map[string]bool)
for _, file := range pkg.Files {
for _, decl := range file.Decls {
addNames(names, decl) // addNames を使用
}
}
return names
}
// collectExamples collects examples for pkg from testfiles.
func collectExamples(pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
var files []*ast.File
for _, f := range testfiles {
files = append(files, f)
}
var examples []*doc.Example
globals := globalNames(pkg) // 簡素化された globalNames を使用
for _, e := range doc.Examples(files...) {
name := stripExampleSuffix(e.Name)
if name == "" || globals[name] {
examples = append(examples, e)
} else {
log.Printf("skipping example Example%s: refers to unknown function or type", e.Name)
}
}
return examples
}
globalNamesは、ast.Packageを直接受け取るように変更され、パッケージ内のすべてのファイルからグローバルな宣言名を効率的に収集します。collectExamplesは、go/buildから提供されるテストファイルのマップを直接受け取り、doc.Examplesを呼び出してサンプルを抽出します。これにより、テストファイルの読み込みと処理がより直接的になりました。
src/cmd/godoc/parser.go の変更点
このファイルはGoソースファイルの解析を担当しており、ファイルシステム操作の抽象化と解析ロジックの変更が行われました。
parseFiles 関数の変更
func parseFiles(fset *token.FileSet, abspath string, localnames []string) (map[string]*ast.File, error) {
files := make(map[string]*ast.File)
for _, f := range localnames {
absname := pathpkg.Join(abspath, f)
file, err := parseFile(fset, absname, parser.ParseComments)
if err != nil {
return nil, err
}
files[absname] = file
}
return files, nil
}
- 以前の
parseFilesは、ファイル名のリストを受け取り、複数のパッケージを返す可能性がありました。新しいparseFilesは、単一のディレクトリ (abspath) とそのディレクトリ内のファイル名 (localnames) のリストを受け取り、map[string]*ast.Fileを返します。これは、go/buildがすでにパッケージの概念を処理しているため、parser.goがより低レベルのファイル解析に集中できるようになったことを示しています。 parseDir関数は完全に削除されました。これは、ディレクトリ内のファイルをフィルタリングして解析するロジックがgo/build.ImportDirに移譲されたためです。
src/cmd/*/doc.go ファイル群の変更
多くのコマンドの doc.go ファイルが変更されました。例:
--- a/src/cmd/5a/doc.go
+++ b/src/cmd/5a/doc.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.\n
+// +build ignore
+\n
/*
5a is a version of the Plan 9 assembler. The original is documented at
@@ -11,4 +13,4 @@
Its target architecture is the ARM, referred to by these tools as arm.\n
*/
-package documentation
+package main
package documentationからpackage mainへの変更: これは、Goで書かれたコマンドのドキュメントが、実際のGoの実行可能ファイルと同じpackage mainに属するように統一されたことを意味します。これにより、godocがコマンドのドキュメントをより自然に認識できるようになります。// +build ignoreの追加: これは、これらのdoc.goファイルがGoのビルドプロセスではコンパイルされないことをGoツールチェーンに指示します。特にC言語で書かれたコマンドの場合、これらのdoc.goファイルはGoのソースコードではないため、ビルドから除外する必要があります。しかし、godocはこれらのファイルをドキュメントとして読み込む必要があります。この制約は、この両方の要件を満たすためのものです。
これらの変更は、godoc の内部処理をより効率的かつ正確にし、Goのビルドシステムとの整合性を高めることを目的としています。
関連リンク
- Go Issue 4806: cmd/godoc: improve command documentation
- Go Change-list 7333046: cmd/godoc: use go/build to determine package and example files
go/buildパッケージのドキュメント: https://pkg.go.dev/go/buildgodocのドキュメント: https://pkg.go.dev/cmd/godoc- "Godoc: documenting Go code" (Goブログ記事): https://golang.org/doc/articles/godoc_documenting_go_code.html
参考にした情報源リンク
- 上記の「関連リンク」セクションに記載されているGoのIssue、Change-list、および公式ドキュメント。
- Go言語のビルド制約に関する一般的な情報源。
- Go言語のパッケージとコマンドの概念に関する一般的な情報源。
go/astおよびgo/docパッケージの機能に関する一般的な情報源。godocツールの動作原理に関する一般的な情報源。- Go言語のソースコードリポジトリ(特に
src/cmd/godocディレクトリ)。 - Go言語のコミット履歴と関連する議論。