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

[インデックス 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のビルドシステムやパッケージの概念と必ずしも一致せず、混乱を招く可能性がありました。

この変更の主な動機は以下の点に集約されます。

  1. go/build パッケージの活用: Go 1.0以降、go/build パッケージはGoのソースファイルをビルドタグや環境変数に基づいてフィルタリングし、パッケージを特定するための標準的な方法を提供しています。godoc がこのパッケージを利用することで、Goのビルドシステムが認識するのと同じ方法でパッケージファイルやサンプルファイルを正確に特定できるようになります。これにより、godoc の動作がより堅牢になり、Goのツールチェーン全体との整合性が向上します。

  2. コマンドドキュメントの簡素化: 以前は、コマンド(実行可能ファイル)のドキュメントは package documentation という特殊なパッケージ名を持つ doc.go ファイルに記述されていました。しかし、Goの実行可能ファイルは常に package main として定義されます。この不一致が、godoc でコマンドのドキュメントが正しく表示されない原因となることがありました。このコミットでは、すべての package main がコマンドとして扱われるようにすることで、このプロセスを簡素化し、より直感的な動作を実現しています。

  3. C言語で書かれたコマンドのドキュメントの扱い: Goのツールチェーンには、C言語で書かれたコマンド(例: 5a, 6c など、Plan 9由来のツール)も含まれています。これらのコマンドのドキュメントも doc.go ファイルに記述されることがありますが、これらはGoのソースファイルではないため、go/build の通常のルールでは処理されません。このコミットでは、これらのファイルに対して +build ignore ビルド制約を使用することを推奨し、godoc がそれらを適切に無視しつつ、package main としてドキュメントを認識できるようにする新しいガイドラインを導入しています。

  4. サンプルコード抽出の高速化: godoc はパッケージのサンプルコード(Example 関数)も表示します。このコミットでは、サンプルコードの抽出ロジックを最適化し、パフォーマンスを向上させています。

これらの変更により、godoc はより正確に、より効率的に、そしてよりGoの慣習に沿った形でドキュメントを生成できるようになります。

前提知識の解説

このコミットの理解には、以下のGo言語および関連ツールの概念に関する知識が不可欠です。

  1. godoc ツール:

    • Go言語の標準ドキュメント生成ツールです。ソースコード内のコメント(特にパッケージ、関数、型、変数などの宣言に付随するコメント)を解析し、HTML形式やプレーンテキスト形式でドキュメントを生成します。
    • 開発者がコードを理解しやすくするために、コードとドキュメントを密接に連携させることを目的としています。
    • godoc は、Goの標準ライブラリだけでなく、ユーザーが作成したパッケージやコマンドのドキュメントも表示できます。
  2. go/build パッケージ:

    • Goの標準ライブラリの一部で、Goのソースコードをビルドするための情報を提供します。
    • 特定のディレクトリ内のGoソースファイルを解析し、どのファイルが特定のビルド環境(OS、アーキテクチャ、ビルドタグなど)でビルドされるべきかを判断する機能を持っています。
    • go/build.Context 構造体は、ビルド環境に関する情報(GOOS, GOARCH など)を含み、ImportDir メソッドは指定されたディレクトリからパッケージ情報を抽出します。
    • GoFiles, CgoFiles, IgnoredGoFiles, TestGoFiles, XTestGoFiles などのフィールドを通じて、ビルド対象となる様々な種類のファイルリストを提供します。
  3. package main とコマンド:

    • Go言語において、package main は実行可能なプログラムのエントリポイントを定義するパッケージです。main パッケージ内の main 関数がプログラムの実行開始点となります。
    • go build コマンドでビルドされると、package main は実行可能バイナリを生成します。このような実行可能バイナリは「コマンド」と呼ばれます。
    • godoc は、パッケージのドキュメントだけでなく、コマンドのドキュメントも表示する機能を持っています。
  4. doc.go ファイル:

    • Goの慣習として、パッケージ全体のドキュメントは、そのパッケージ内の任意のGoソースファイル(通常は doc.go という名前のファイル)のパッケージ宣言の直前に記述されたコメントブロックに含めることができます。
    • このコメントは、godoc によってパッケージの概要として表示されます。
    • 以前は、コマンドのドキュメントのために package documentation という特殊なパッケージ名が使われることがありましたが、このコミットで package main に統一されます。
  5. ビルド制約 (+build タグ):

    • Goのソースファイルには、ファイルの先頭に +build タグを記述することで、特定のビルド環境でのみそのファイルをコンパイルするように指示できます。
    • 例: // +build linux,amd64 はLinuxかつAMD64アーキテクチャでのみコンパイルされます。
    • // +build ignore は、そのファイルをGoのビルドプロセスから完全に除外するための特別な制約です。これは、Goのソースファイルではないが、リポジトリ内に存在する必要があるファイル(例: ドキュメントファイル、テストデータなど)によく使用されます。このコミットでは、C言語で書かれたコマンドのドキュメントファイルに対してこの制約の使用が推奨されています。
  6. 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.gosrc/cmd/godoc/dirtrees.gosrc/cmd/godoc/parser.go の3つのファイルに集中しており、godoc がパッケージ情報とサンプルコードをどのように取得・処理するかを根本的に変更しています。

  1. go/build パッケージの導入と利用:

    • 最も重要な変更は、godoc がパッケージのGoソースファイルを特定するために、独自のファイルフィルタリングロジックから go/build パッケージの build.Context を使用するように移行した点です。
    • src/cmd/godoc/godoc.gogetPageInfo 関数内で、build.Default コンテキストが初期化され、ctxt.ReadDirctxt.OpenFilegodoc 独自のファイルシステム (fs) をラップした関数が設定されます。これにより、go/buildgodoc の仮想ファイルシステムを通じてファイルを読み込めるようになります。
    • ctxt.ImportDir(abspath, 0) を呼び出すことで、指定されたディレクトリ (abspath) からビルド可能なGoファイル(GoFiles, CgoFiles)と無視されるGoファイル(IgnoredGoFiles)を含む build.Package 情報が取得されます。これにより、godoc は現在のビルド環境(GOOS, GOARCH など)に合わせた正確なファイルセットを自動的に選択できるようになります。
  2. コマンドドキュメントの扱いの一元化:

    • 以前は、godocfakePkgName = "documentation" という特別なパッケージ名を使用してコマンドのドキュメントを識別していました。このコミットでは、この概念が削除され、すべての package main がコマンドとして扱われるようになります。
    • lib/godoc/package.html および lib/godoc/package.txt のテンプレートで、$.IsPkg の代わりに not $.IsMain が使用されるようになり、main パッケージではない場合にのみ "PACKAGE" ヘッダーが表示されるようになります。
    • PageInfo 構造体に IsMain bool フィールドが追加され、getPageInfo 関数内で pkgname == "main" の場合に true が設定されます。これにより、godocmain パッケージをコマンドとして適切に表示できるようになります。
    • 多くの src/cmd/*/doc.go ファイルが package documentation から package main に変更され、さらに // +build ignore 制約が追加されています。これは、これらのファイルがGoのビルドプロセスではコンパイルされないが、godoc がドキュメントとして読み込むべきファイルであることを示しています。特にC言語で書かれたコマンドのドキュメントに対してこのアプローチが推奨されます。
  3. サンプル抽出の高速化と簡素化:

    • src/cmd/godoc/godoc.goparseExamples 関数が collectExamples にリネームされ、そのロジックが大幅に簡素化されています。
    • 以前は、parseExamplesparseDir を呼び出してテストファイルをフィルタリングしていましたが、新しい collectExamplesgo/build から取得した pkginfo.TestGoFilespkginfo.XTestGoFiles を直接使用してテストファイルを読み込みます。これにより、ファイルシステムのスキャンが効率化されます。
    • globalNames 関数も簡素化され、map[string]bool を直接受け取るように変更され、declNames の代わりに addNames が使用されるようになりました。これにより、グローバルな宣言名の収集がより効率的になります。
  4. parser.go の変更:

    • src/cmd/godoc/parser.go から os パッケージのインポートが削除されました。これは、ファイルシステム操作が godocfs インターフェースを通じて行われるため、直接 os を使用する必要がなくなったことを示しています。
    • parseFiles 関数が変更され、abspathlocalnames のリストを受け取るようになりました。これにより、go/build が提供するファイルリストを直接利用してファイルを解析できるようになります。
    • parseDir 関数が削除されました。これは、ディレクトリ内のファイルをフィルタリングして解析するロジックが go/buildImportDir に置き換えられたためです。
  5. dirtrees.go の変更:

    • treeBuilder.newDirTree 関数内で、synopses 配列のサイズが [4]string から [3]string に変更されました。これは、fakePkgName の概念が削除されたことによるものです。
    • パッケージの優先順位付けロジックが調整され、fakePkgName に関連するケースが削除されました。

これらの変更は、godoc の内部構造をより現代的なGoのビルドシステムと整合させ、パフォーマンスと保守性を向上させることを目的としています。特に、go/build の採用は、godoc がGoのツールチェーンの他の部分とよりシームレスに連携するための重要なステップです。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。

  1. src/cmd/godoc/godoc.go:

    • PageInfo 構造体の変更: IsPkg bool が削除され、IsMain bool が追加されました。また、FSet, PDoc, PAst, Examples のフィールドが Err の後に移動し、ErrPageInfo の最初のフィールドになりました。
    • docServer 構造体の変更: isPkg bool フィールドが削除されました。
    • getPageInfo 関数の大幅な書き換え:
      • go/build.Default コンテキストを使用して、ctxt.ImportDir を呼び出し、パッケージのGoファイル、Cgoファイル、無視されるGoファイル、テストGoファイル、XTestGoファイルを取得するようになりました。
      • 以前のファイルフィルタリングロジック(filter 関数)が削除されました。
      • parseExamplescollectExamples に置き換えられ、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 を受け取るように変更されました。
  2. src/cmd/godoc/dirtrees.go:

    • treeBuilder.newDirTree 関数内の synopses 配列のサイズが [4]string から [3]string に変更されました。
    • fakePkgName に関連するパッケージの優先順位付けロジックが削除されました。
  3. src/cmd/godoc/parser.go:

    • os パッケージのインポートが削除されました。
    • parseFiles 関数のシグネチャと実装が変更され、abspathlocalnames のリストを受け取るようになりました。
    • parseDir 関数が完全に削除されました。
  4. lib/godoc/package.html および lib/godoc/package.txt:

    • {{if $.IsPkg}}{{if not $.IsMain}} に変更され、main パッケージではない場合にのみパッケージ情報が表示されるようになりました。
  5. src/cmd/*/doc.go ファイル群:

    • 多くのコマンドの doc.go ファイルで、package documentationpackage 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 ファイルがコマンドのドキュメントとして適切に扱われます。
  • サンプル抽出の改善: parseExamplescollectExamples に置き換えられ、go/build から取得した TestGoFilesXTestGoFiles を直接使用するようになりました。これにより、テストファイルの特定と解析がより効率的になります。
  • IsMain の設定: pkgname == "main" に基づいて info.IsMain が設定されるようになり、godocmain パッケージをコマンドとして適切に識別できるようになりました。

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、Change-list、および公式ドキュメント。
  • Go言語のビルド制約に関する一般的な情報源。
  • Go言語のパッケージとコマンドの概念に関する一般的な情報源。
  • go/ast および go/doc パッケージの機能に関する一般的な情報源。
  • godoc ツールの動作原理に関する一般的な情報源。
  • Go言語のソースコードリポジトリ(特に src/cmd/godoc ディレクトリ)。
  • Go言語のコミット履歴と関連する議論。