[インデックス 1944] ファイルの概要
このコミットは、Go言語の初期開発段階におけるドキュメンテーションサーバーの重要な変更を記録しています。具体的には、既存のgds
(Go Documentation Server)をgodoc
という名称に刷新し、内部的なAST(抽象構文木)の変更に対応させるとともに、パッケージの検索と提供機能を追加しています。これにより、Goのソースコードから自動的にドキュメントを生成し、Webブラウザを通じて閲覧できる現在のgodoc
ツールの基礎が築かれました。
コミット
commit 695c90daa0d9408e5144bf94ad797b33fbdb5a8f
Author: Robert Griesemer <gri@golang.org>
Date: Thu Apr 2 15:58:58 2009 -0700
- adjustments for changed AST
- renamed gds -> godoc
- functionality to find and serve packages
(to get a list of packages provide dir path + "?p")
Next steps: cleanups, better formatting, fine-tuning of output
R=r
OCL=27037
CL=27039
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/695c90daa0d9408e5144bf94ad797b33fbdb5a8f
元コミット内容
このコミットの元々の内容は以下の3点に集約されます。
- AST(抽象構文木)の変更への対応: Goコンパイラやツールチェインの内部で利用されるASTの構造が変更されたことに伴い、ドキュメンテーションサーバーもその変更に適応するための調整が行われました。
gds
からgodoc
への名称変更: 以前はgds
という名前であったドキュメンテーションサーバーがgodoc
に改名されました。これは、Goコミュニティにおけるこのツールの標準的な名称として定着することになります。- パッケージ検索・提供機能の追加: ディレクトリパスに
?p
クエリパラメータを付与することで、そのディレクトリ内のパッケージリストを取得し、提供する機能が追加されました。これにより、Goのソースコードベースを探索し、ドキュメントを生成する能力が向上しました。
変更の背景
このコミットが行われた2009年4月は、Go言語がまだ一般に公開される前の初期開発段階でした。この時期は、言語仕様、コンパイラ、標準ライブラリ、そして開発ツールの基礎が精力的に構築されていたフェーズです。
変更の背景には、以下の要因が考えられます。
- 言語仕様の進化とASTの安定化: Go言語の仕様は初期段階で頻繁に変更されており、それに伴いコンパイラが生成するASTの構造も進化していました。ドキュメンテーションツールはASTを解析して情報を抽出するため、ASTの変更に追随する必要がありました。このコミットは、ASTの特定の変更(例えば、識別子やリテラルの表現方法)に対応するためのものです。
- ツールの体系化とブランド化:
gds
という名称は汎用的であり、Go言語の公式ドキュメンテーションツールとしてのアイデンティティを確立するためには、よりGoらしい、覚えやすい名称が必要でした。godoc
という名称は、Go言語のドキュメントを扱うツールであることを明確に示し、その後のGoエコシステムにおける重要なツールとしてのブランドを確立しました。 - 開発効率の向上: Go言語のプロジェクトが成長するにつれて、コードベースの理解とナビゲーションの重要性が増していました。ソースコードから直接ドキュメントを生成し、Webインターフェースで提供する
godoc
のようなツールは、開発者がGoの標準ライブラリやサードパーティパッケージのAPIを素早く参照し、理解する上で不可欠です。パッケージの検索・提供機能は、この開発効率向上に直接貢献します。
前提知識の解説
このコミットの理解には、以下の概念に関する前提知識が役立ちます。
- AST (Abstract Syntax Tree - 抽象構文木): プログラミング言語のソースコードを解析して得られる、プログラムの構造を木構造で表現したものです。コンパイラやリンター、コード分析ツールなどは、このASTを利用してコードの意味を理解し、処理を行います。Go言語のツールチェインでは、
go/ast
パッケージがASTの定義と操作を提供します。 - Go言語のパッケージシステム: Go言語は、コードを
package
という単位で整理します。各Goファイルは必ずpackage
宣言を持ち、関連する機能は同じパッケージにまとめられます。パッケージは、コードの再利用性、モジュール性、名前空間の管理に貢献します。 godoc
ツール: Go言語の公式ドキュメンテーションツールです。Goのソースコード内のコメント(特にエクスポートされた識別子の直前のコメント)を解析し、HTML形式のドキュメントを自動生成します。これにより、開発者はコードとドキュメントを密接に連携させることができます。godoc
は、ローカルでドキュメントサーバーを起動してブラウザで閲覧することも、静的なHTMLファイルを生成することも可能です。http
パッケージ (Go標準ライブラリ): Go言語の標準ライブラリに含まれるHTTPクライアントおよびサーバー機能を提供するパッケージです。このコミットでは、godoc
がWebサーバーとして機能するためにhttp
パッケージが利用されています。template
パッケージ (Go標準ライブラリ): Go言語の標準ライブラリに含まれる、HTMLなどのテキストテンプレートを扱うためのパッケージです。このコミットでは、godoc
がWebページを動的に生成するためにテンプレートエンジンを使用しています。os
パッケージ (Go標準ライブラリ): オペレーティングシステムと対話するための機能(ファイルシステム操作、環境変数など)を提供するパッケージです。godoc
がファイルシステムを走査してGoソースファイルやディレクトリを読み込むために使用されます。utf8
パッケージ (Go標準ライブラリ): UTF-8エンコーディングされたテキストを扱うためのユーティリティを提供するパッケージです。Goの識別子や文字列リテラルはUTF-8で表現されるため、文字のデコードに利用されます。unicode
パッケージ (Go標準ライブラリ): Unicode文字のプロパティ(例えば、大文字であるか否か)を扱うためのパッケージです。Goのエクスポートされた識別子は大文字で始まるという規則があるため、このパッケージが利用されます。
技術的詳細
このコミットの技術的詳細は、主に以下の3つの側面から分析できます。
-
AST構造の変更への対応 (
ast.Ident
のLit
からValue
への変更):- コミットの差分を見ると、
astprinter.go
とdocprinter.go
において、ast.Ident
(識別子)のフィールドがLit
からValue
に変更されている箇所が多数見られます。 - 例えば、
name.Lit
がname.Value
に、x.Lit
がx.Value
に、string(t.Lit)
がstring(t.Value)
に変更されています。 - これは、Go言語のAST表現において、識別子やリテラルの「文字列表現」を保持するフィールドの名称が変更されたことを意味します。初期のGoコンパイラでは、リテラル値が
Lit
(Literalの略)というフィールドに格納されていた可能性がありますが、より汎用的なValue
という名称に変更されたと考えられます。この変更は、ASTの設計がより洗練され、安定化に向かっていたことを示唆しています。 - また、
utf8.DecodeRune(name.Lit)
がutf8.DecodeRuneInString(name.Value, 0)
に変更されています。これは、utf8.DecodeRune
が[]byte
を受け取るのに対し、utf8.DecodeRuneInString
はstring
を受け取る関数であり、ast.Ident
のフィールドが[]byte
からstring
に変更されたことを示唆しています。これにより、文字列操作がより直接的かつ安全に行えるようになりました。
- コミットの差分を見ると、
-
gds
からgodoc
へのリファクタリングと機能拡張:usr/gri/pretty/gds.go
が削除され、代わりにusr/gri/pretty/godoc.go
が新規作成されています。これは単なるファイル名の変更ではなく、既存のコードベースを基盤としつつ、大幅な機能追加と構造の改善が行われたことを示しています。- 新しい
godoc.go
には、パッケージを検索し、そのドキュメントを提供するロジックが追加されています。 makePackageMap()
関数が導入され、Goのソースツリーを走査してパッケージ情報を収集し、pakMap
(パッケージ名からパッケージ記述子へのマップ)とpakList
(ソートされたパッケージのリスト)を構築しています。これは、godoc
がGoのソースコードベース全体を理解し、効率的にパッケージを検索できるようにするための重要なステップです。addDirectory()
とaddFile()
関数は、再帰的にディレクトリを走査し、Goファイルを解析してパッケージ情報を抽出する役割を担っています。serveGoPackage()
関数は、特定のGoパッケージのドキュメントを生成して提供します。servePackageList()
関数は、検索条件に合致するパッケージのリストをHTML形式で表示します。regexp
パッケージがインポートされ、パッケージ検索に正規表現が利用できるようになっています。これにより、ユーザーは柔軟な条件でパッケージを検索できるようになりました。
-
Webサーバーとしての機能強化:
godoc
はHTTPサーバーとして動作し、http.Handle("/", http.HandlerFunc(serve))
でルートパスに対するリクエストをserve
関数で処理するように設定されています。serve
関数は、リクエストのURLパスとクエリパラメータを解析し、ディレクトリのリスト、個別のGoファイルのドキュメント、またはパッケージのリスト/ドキュメントを提供します。- 特に、
req.Url.Query
が存在する場合(例:?p
クエリパラメータ)、servePackage
関数が呼び出され、パッケージ関連の機能が提供されるようになっています。 packages_template.html
という新しいHTMLテンプレートが追加されており、パッケージリストの表示に使用されます。これにより、godoc
の出力がより構造化され、視覚的に分かりやすくなりました。
これらの変更は、godoc
が単なるファイルビューアから、Goエコシステムにおける強力なドキュメンテーションおよびコード探索ツールへと進化する上で不可欠なものでした。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
usr/gri/pretty/astprinter.go
:ast.Ident
のLit
フィールドからValue
フィールドへの参照変更。utf8.DecodeRune
からutf8.DecodeRuneInString
への変更。- リテラル(
IntLit
,FloatLit
,CharLit
,StringLit
)のLit
フィールドからValue
フィールドへの参照変更。 HtmlPackageName
におけるd.Path[0].Lit
からd.Path[0].Value
への変更。
-
usr/gri/pretty/docprinter.go
:ast.Ident
のLit
フィールドからValue
フィールドへの参照変更。utf8.DecodeRune
からutf8.DecodeRuneInString
への変更。baseTypeName
関数におけるt.Lit
からt.Value
への変更。addType
、addFunc
、AddProgram
関数におけるLit
フィールドからValue
フィールドへの参照変更。funcDoc
とtypeDoc
のprint
メソッドにおけるLit
フィールドからValue
フィールドへの参照変更。
-
usr/gri/pretty/gds.go
:- ファイル全体が削除されました。
-
usr/gri/pretty/godoc.go
:- ファイル全体が新規作成されました。これが新しい
godoc
サーバーの実装です。 - 特に、
makePackageMap()
、addDirectory()
、addFile()
、serveGoPackage()
、servePackageList()
、servePackage()
といったパッケージ関連の新しい関数が追加されています。 regexp
パッケージのインポートと利用。
- ファイル全体が新規作成されました。これが新しい
-
usr/gri/pretty/Makefile
:gds
ターゲットがgodoc
ターゲットに名称変更され、関連するビルドコマンドも変更されました。
-
usr/gri/pretty/packages_template.html
:- パッケージリスト表示用の新しいHTMLテンプレートファイルが追加されました。
コアとなるコードの解説
AST変更への対応例 (astprinter.go
より)
// Before:
func isExported(name *ast.Ident) bool {
ch, len := utf8.DecodeRune(name.Lit);
return unicode.IsUpper(ch);
}
// After:
func isExported(name *ast.Ident) bool {
ch, len := utf8.DecodeRuneInString(name.Value, 0);
return unicode.IsUpper(ch);
}
この変更は、ast.Ident
構造体の内部表現がLit
([]byte
型と推測される)からValue
(string
型と推測される)に変わったことを示しています。utf8.DecodeRune
は[]byte
スライスからルーンをデコードしますが、utf8.DecodeRuneInString
はstring
からデコードします。この変更により、ASTの識別子フィールドがよりGoの文字列型に即した形になったことがわかります。
gds
からgodoc
への移行とパッケージ機能の追加 (godoc.go
より)
godoc.go
は、以前のgds.go
の機能を継承しつつ、大幅に拡張されています。
// 新規追加されたパッケージ関連のデータ構造
type pakDesc struct {
dirname string; // local to *root
pakname string; // local to directory
filenames map[string] bool; // set of file (names) belonging to this package
}
var (
pakMap map[string]*pakDesc; // dirname/pakname -> package descriptor
pakList pakArray; // sorted list of packages; in sync with pakMap
)
// パッケージマップを構築する主要な関数
func makePackageMap() {
// TODO shold do this under a lock, eventually
// populate package map
pakMap = make(map[string]*pakDesc);
addDirectory(""); // ルートディレクトリから再帰的にパッケージを収集
// build sorted package list
pakList = make([]*pakDesc, len(pakMap));
i := 0;
for tmp, pakdesc := range pakMap {
pakList[i] = pakdesc;
i++;
}
sort.Sort(pakList); // パッケージリストをソート
}
// ディレクトリを走査し、GoファイルをaddFileに渡す
func addDirectory(dirname string) {
// ... (ディレクトリのオープン、読み込み、再帰処理) ...
for i, entry := range list {
switch {
case entry.IsDirectory():
if entry.Name != "." && entry.Name != ".." {
addDirectory(dirname + "/" + entry.Name);
}
case isGoFile(&entry):
addFile(dirname, entry.Name); // Goファイルを見つけたらaddFileに渡す
}
}
}
// Goファイルを解析し、パッケージ情報をpakMapに追加する
func addFile(dirname string, filename string) {
// determine package name
prog := getAST(dirname, filename, parser.PackageClauseOnly); // ASTを解析してパッケージ名を取得
if prog == nil {
return;
}
if prog.Name.Value == "main" {
// ignore main packages for now
return;
}
pakname := dirname + "/" + prog.Name.Value; // パッケージのユニークな識別子を生成
// find package descriptor
pakdesc, found := pakMap[pakname];
if !found {
// add a new descriptor
pakdesc = &pakDesc{dirname, prog.Name.Value, make(map[string]bool)};
pakMap[pakname] = pakdesc;
}
// add file to package desc
if tmp, found := pakdesc.filenames[filename]; found {
panic("internal error: same file added more then once: " + filename);
}
pakdesc.filenames[filename] = true; // ファイルをパッケージ記述子に追加
}
// HTTPリクエストを処理するメインのハンドラ
func serve(c *http.Conn, req *http.Request) {
// ... (ログ出力、パスのサニタイズ) ...
if len(req.Url.Query) > 0 { // クエリパラメータがある場合
servePackage(c, path); // パッケージ関連の処理
} else {
serveFile(c, path); // ファイルまたはディレクトリの処理
}
}
// パッケージ検索と表示を制御する関数
func servePackage(c *http.Conn, path string) {
// make regexp for package matching
rex, err := regexp.Compile(path); // パスを正規表現としてコンパイル
// ... (エラーハンドリング) ...
// build list of matching packages
list := vector.New(0);
for i, p := range pakList {
if rex.Match(p.dirname + "/" + p.pakname) { // 正規表現にマッチするパッケージを収集
list.Push(p);
}
}
if list.Len() == 1 {
serveGoPackage(c, list.At(0).(*pakDesc)); // マッチが1つならそのパッケージのドキュメントを表示
} else {
servePackageList(c, list); // 複数マッチならパッケージリストを表示
}
}
これらのコードは、godoc
がGoのソースツリーを走査し、パッケージ構造を理解し、それに基づいてドキュメントを生成・提供する仕組みの核心を示しています。特に、makePackageMap
による初期のパッケージ情報の収集と、servePackage
による柔軟なパッケージ検索機能は、godoc
の利便性を大きく向上させました。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
godoc
コマンドのドキュメント: https://pkg.go.dev/cmd/godoc- Go言語のASTパッケージ (
go/ast
): https://pkg.go.dev/go/ast - Go言語のパーサーパッケージ (
go/parser
): https://pkg.go.dev/go/parser
参考にした情報源リンク
- Go言語の初期開発に関する情報(Goの歴史など)
- Go言語のASTの進化に関する議論やドキュメント
godoc
ツールの設計思想に関する記事やプレゼンテーション- Go言語の
http
、template
、os
、utf8
、unicode
パッケージの公式ドキュメント
(注:具体的な情報源URLは、当時の情報がウェブ上に残っているか、またその情報が公開されているかによって異なります。この解説は、一般的なGo言語の知識と、コミット内容から推測される背景に基づいて記述されています。)