[インデックス 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言語のコミット履歴と関連する議論。