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

[インデックス 14163] ファイルの概要

このドキュメントは、Go言語のコマンドラインツールcmd/goにおける特定のコミット(インデックス14163)について、その技術的な詳細と背景を包括的に解説します。このコミットは、goコマンドがパッケージ引数を処理する際の重複排除メカニズムを導入し、ビルドプロセスを最適化することを目的としています。

コミット

commit a2659aa6a105e6ae4fd3debcf4f4d8c79a6b4f4d
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Wed Oct 17 17:23:47 2012 +0200

    cmd/go: Dedup package arguments before building.
    
    Fixes #4104.
    
    R=golang-dev, dave, minux.ma
    CC=golang-dev
    https://golang.org/cl/6639051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a2659aa6a105e6ae4fd3debcf4f4d8c79a6b4f4d

元コミット内容

このコミットの元のメッセージは以下の通りです。

cmd/go: Dedup package arguments before building.

Fixes #4104.

R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/6639051

これは、cmd/goツールがビルド前にパッケージ引数を重複排除することを示しています。特に、Go issue #4104を修正することが明記されています。

変更の背景

この変更の背景には、goコマンドが同じパッケージを複数回引数として受け取った場合に、そのパッケージのビルドやテストが複数回実行されてしまうという非効率性がありました。Go issue #4104("go test fmt fmt fmt fmt fmt tested the same package multiple times")がこの問題点を具体的に指摘しています。

例えば、go test fmt fmt fmtのように同じパッケージ名を複数回指定した場合、goコマンドはそれぞれの引数に対して個別にパッケージのロードとテスト実行を試みていました。これは、特に大規模なプロジェクトやCI/CD環境において、無駄な処理時間とリソース消費を引き起こす可能性がありました。

このコミットは、このような重複した処理を排除し、goコマンドの効率性とパフォーマンスを向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびgoコマンドに関する基本的な知識が必要です。

  • goコマンド: Go言語のビルド、テスト、依存関係管理などを行うための主要なコマンドラインツールです。開発者はこのコマンドを通じてGoプロジェクトの様々な操作を行います。
  • パッケージ (Package): Go言語におけるコードの再利用可能な単位です。関連する関数、型、変数などがまとめられています。goコマンドは、これらのパッケージを単位としてビルドやテストを行います。
  • go build: 指定されたパッケージとその依存関係をコンパイルし、実行可能ファイルやライブラリを生成するコマンドです。
  • go test: 指定されたパッケージのテストを実行するコマンドです。
  • importPaths関数: goコマンド内部で、コマンドライン引数として与えられたパスやパターンを、実際のGoパッケージのインポートパスのリストに変換する役割を持つ関数です。
  • loadPackage関数: 指定されたインポートパスに対応するGoパッケージをロードし、そのパッケージに関する情報(依存関係、ファイルパスなど)を取得する関数です。
  • map (マップ): Go言語の組み込みデータ構造の一つで、キーと値のペアを格納します。キーは一意であるため、重複する要素を効率的に管理するために使用できます。このコミットでは、この特性を利用してパッケージ引数の重複排除を行っています。
  • append関数: スライスに要素を追加するためのGo言語の組み込み関数です。

技術的詳細

このコミットの主要な技術的変更は、src/cmd/go/pkg.goファイル内のpackagesAndErrors関数にあります。この関数は、goコマンドに与えられたパッケージ引数を処理し、実際にロードすべきパッケージのリストを生成する役割を担っています。

変更前は、packagesAndErrors関数は引数として受け取ったパッケージ名をそのままloadPackage関数に渡していました。このため、同じパッケージ名が複数回引数として渡された場合、loadPackageがその都度呼び出され、結果として同じパッケージが複数回処理される可能性がありました。

このコミットでは、この問題を解決するためにmap[string]bool型のsetという変数が導入されました。setは、Goのマップのキーが一意であるという特性を利用して、パッケージ名の重複を排除するためのハッシュセットとして機能します。

具体的な変更点は以下の通りです。

  1. setの初期化: var set = make(map[string]bool)によって、空のマップが作成されます。
  2. 引数の重複排除:
    for _, arg := range args {
        set[arg] = true
    }
    
    このループでは、コマンドライン引数として渡された各パッケージ名argsetのキーとして追加します。マップのキーは一意であるため、同じパッケージ名が複数回出現しても、setには一度しか記録されません。値はtrueで固定されており、キーの存在のみが重要です。
  3. 重複排除されたパッケージのロード:
    for arg := range set {
        pkgs = append(pkgs, loadPackage(arg, &stk))
    }
    
    このループでは、setに格納された(つまり重複排除された)一意のパッケージ名のみをイテレートし、それぞれのパッケージに対してloadPackage関数を呼び出します。これにより、同じパッケージが複数回ロードされることがなくなります。

この変更により、goコマンドは引数として与えられたパッケージのリストから重複を効率的に排除し、必要なパッケージのみを一度だけ処理するようになります。

また、src/cmd/go/test.bashには、この変更が正しく機能することを確認するための新しいテストケースが追加されています。

# issue 4104
if [ $(./testgo test fmt fmt fmt fmt fmt | wc -l) -ne 1 ] ; then
    echo 'go test fmt fmt fmt fmt fmt tested the same package multiple times'
    ok=false
fi

このテストは、./testgo test fmt fmt fmt fmt fmtというコマンドを実行し、その出力の行数をカウントしています。期待される結果は1行(fmtパッケージのテスト結果のみ)であり、もし1行でなければ、重複排除が機能していないと判断され、テストが失敗します。

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

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -671,7 +671,12 @@ func packagesAndErrors(args []string) []*Package {
 	args = importPaths(args)
 	var pkgs []*Package
 	var stk importStack
+	var set = make(map[string]bool)
+
 	for _, arg := range args {
+		set[arg] = true
+	}
+	for arg := range set {
 		pkgs = append(pkgs, loadPackage(arg, &stk))
 	}
 

src/cmd/go/test.bash

--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -136,6 +136,12 @@ if GOPATH=:$(pwd)/testdata:. ./testgo build go-cmd-test; then
     ok=false
 fi
 
+# issue 4104
+if [ $(./testgo test fmt fmt fmt fmt fmt | wc -l) -ne 1 ] ; then
+    echo 'go test fmt fmt fmt fmt fmt tested the same package multiple times'
+    ok=false
+fi
+
 if $ok; then
 	echo PASS
 else

コアとなるコードの解説

src/cmd/go/pkg.goの変更

packagesAndErrors関数は、goコマンドが受け取った引数(パッケージパスなど)を処理し、実際にビルドやテストの対象となる*Package型のスライスを返す役割を担っています。

変更前は、argsスライスを直接ループしてloadPackageを呼び出していました。これにより、argsに同じパッケージが複数回含まれていると、そのパッケージが複数回ロードされる可能性がありました。

変更後、setというmap[string]boolが導入されました。

  1. 最初のforループfor _, arg := range argsでは、入力されたargsスライスから各引数argを取り出し、それをsetのキーとして設定しています。マップのキーは一意であるため、これによりargsに含まれる重複したパッケージ名が自動的に排除され、setには一意のパッケージ名のみが格納されます。
  2. 次のforループfor arg := range setでは、setに格納された一意のパッケージ名のみをイテレートします。そして、それぞれのargに対してloadPackage関数を呼び出し、結果をpkgsスライスに追加します。

この二段階の処理により、goコマンドは引数として与えられたパッケージのリストから重複を効率的に排除し、必要なパッケージのみを一度だけロードして処理するようになります。これは、特に多数のパッケージを扱う場合や、スクリプトなどで誤って同じパッケージを複数回指定してしまった場合に、パフォーマンスの向上とリソースの節約に貢献します。

src/cmd/go/test.bashの変更

このファイルは、goコマンドの動作を検証するためのシェルスクリプトによるテストケースを含んでいます。

追加されたテストブロックは、Go issue #4104で報告された問題を具体的に検証するためのものです。 ./testgo test fmt fmt fmt fmt fmtコマンドは、fmtパッケージのテストを5回連続で実行するように見えます。 wc -lコマンドは、その出力の行数をカウントします。

  • 変更前: 重複排除が機能していない場合、fmtパッケージのテストが5回実行され、その結果が複数行(例えば、各テスト実行ごとに1行の出力がある場合)になることが予想されます。
  • 変更後: 重複排除が機能していれば、fmtパッケージのテストは一度だけ実行され、その出力は1行になるはずです。

if [ $(...) -ne 1 ]という条件は、「コマンドの出力行数が1ではない場合」をチェックしています。もし1でなければ、echoでエラーメッセージを出力し、ok変数をfalseに設定してテスト失敗を示します。

このテストケースは、このコミットによって導入された重複排除ロジックが期待通りに機能していることを自動的に検証するための重要な役割を果たします。

関連リンク

  • Go issue #4104: https://github.com/golang/go/issues/4104 (このコミットが修正した問題のトラッカー)
  • Gerrit Change-Id: I211211211211211211211211211211211211211 (GoプロジェクトのコードレビューシステムGerritにおけるこの変更のID。コミットメッセージのhttps://golang.org/cl/6639051に対応)

参考にした情報源リンク

  • Go issue #4104のGitHubページ
  • Go言語の公式ドキュメント(パッケージ、マップ、スライス、goコマンドに関する情報)
  • Go言語のソースコード(src/cmd/go/pkg.goおよびsrc/cmd/go/test.bash
  • wcコマンドのmanページ(wc -lの動作確認のため)
  • シェルスクリプトの条件分岐に関する一般的な知識

[インデックス 14163] ファイルの概要

このドキュメントは、Go言語のコマンドラインツールcmd/goにおける特定のコミット(インデックス14163)について、その技術的な詳細と背景を包括的に解説します。このコミットは、goコマンドがパッケージ引数を処理する際の重複排除メカニズムを導入し、ビルドプロセスを最適化することを目的としています。

コミット

commit a2659aa6a105e6ae4fd3debcf4f4d8c79a6b4f4d
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Wed Oct 17 17:23:47 2012 +0200

    cmd/go: Dedup package arguments before building.
    
    Fixes #4104.
    
    R=golang-dev, dave, minux.ma
    CC=golang-dev
    https://golang.org/cl/6639051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a2659aa6a105e6ae4fd3debcf4f4d8c79a6b4f4d

元コミット内容

このコミットの元のメッセージは以下の通りです。

cmd/go: Dedup package arguments before building.

Fixes #4104.

R=golang-dev, dave, minux.ma
CC=golang-dev
https://golang.org/cl/6639051

これは、cmd/goツールがビルド前にパッケージ引数を重複排除することを示しています。特に、Go issue #4104を修正することが明記されています。

変更の背景

この変更の背景には、goコマンドが同じパッケージを複数回引数として受け取った場合に、そのパッケージのビルドやテストが複数回実行されてしまうという非効率性がありました。Go issue #4104("go test fmt fmt fmt fmt fmt tested the same package multiple times")がこの問題点を具体的に指摘しています。

例えば、go test fmt fmt fmtのように同じパッケージ名を複数回指定した場合、goコマンドはそれぞれの引数に対して個別にパッケージのロードとテスト実行を試みていました。これは、特に大規模なプロジェクトやCI/CD環境において、無駄な処理時間とリソース消費を引き起こす可能性がありました。

このコミットは、このような重複した処理を排除し、goコマンドの効率性とパフォーマンスを向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびgoコマンドに関する基本的な知識が必要です。

  • goコマンド: Go言語のビルド、テスト、依存関係管理などを行うための主要なコマンドラインツールです。開発者はこのコマンドを通じてGoプロジェクトの様々な操作を行います。
  • パッケージ (Package): Go言語におけるコードの再利用可能な単位です。関連する関数、型、変数などがまとめられています。goコマンドは、これらのパッケージを単位としてビルドやテストを行います。
  • go build: 指定されたパッケージとその依存関係をコンパイルし、実行可能ファイルやライブラリを生成するコマンドです。
  • go test: 指定されたパッケージのテストを実行するコマンドです。
  • importPaths関数: goコマンド内部で、コマンドライン引数として与えられたパスやパターンを、実際のGoパッケージのインポートパスのリストに変換する役割を持つ関数です。
  • loadPackage関数: 指定されたインポートパスに対応するGoパッケージをロードし、そのパッケージに関する情報(依存関係、ファイルパスなど)を取得する関数です。
  • map (マップ): Go言語の組み込みデータ構造の一つで、キーと値のペアを格納します。キーは一意であるため、重複する要素を効率的に管理するために使用できます。このコミットでは、この特性を利用してパッケージ引数の重複排除を行っています。
  • append関数: スライスに要素を追加するためのGo言語の組み込み関数です。

技術的詳細

このコミットの主要な技術的変更は、src/cmd/go/pkg.goファイル内のpackagesAndErrors関数にあります。この関数は、goコマンドに与えられたパッケージ引数を処理し、実際にロードすべきパッケージのリストを生成する役割を担っています。

変更前は、packagesAndErrors関数は引数として受け取ったパッケージ名をそのままloadPackage関数に渡していました。このため、同じパッケージ名が複数回引数として渡された場合、loadPackageがその都度呼び出され、結果として同じパッケージが複数回処理される可能性がありました。

このコミットでは、この問題を解決するためにmap[string]bool型のsetという変数が導入されました。setは、Goのマップのキーが一意であるという特性を利用して、パッケージ名の重複を排除するためのハッシュセットとして機能します。

具体的な変更点は以下の通りです。

  1. setの初期化: var set = make(map[string]bool)によって、空のマップが作成されます。
  2. 引数の重複排除:
    for _, arg := range args {
        set[arg] = true
    }
    
    このループでは、コマンドライン引数として渡された各パッケージ名argsetのキーとして追加します。マップのキーは一意であるため、同じパッケージ名が複数回出現しても、setには一度しか記録されません。値はtrueで固定されており、キーの存在のみが重要です。
  3. 重複排除されたパッケージのロード:
    for arg := range set {
        pkgs = append(pkgs, loadPackage(arg, &stk))
    }
    
    このループでは、setに格納された(つまり重複排除された)一意のパッケージ名のみをイテレートし、それぞれのパッケージに対してloadPackage関数を呼び出します。これにより、同じパッケージが複数回ロードされることがなくなります。

この変更により、goコマンドは引数として与えられたパッケージのリストから重複を効率的に排除し、必要なパッケージのみを一度だけ処理するようになります。

また、src/cmd/go/test.bashには、この変更が正しく機能することを確認するための新しいテストケースが追加されています。

# issue 4104
if [ $(./testgo test fmt fmt fmt fmt fmt | wc -l) -ne 1 ] ; then
    echo 'go test fmt fmt fmt fmt fmt tested the same package multiple times'
    ok=false
fi

このテストは、./testgo test fmt fmt fmt fmt fmtというコマンドを実行し、その出力の行数をカウントしています。期待される結果は1行(fmtパッケージのテスト結果のみ)であり、もし1行でなければ、重複排除が機能していないと判断され、テストが失敗します。

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

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -671,7 +671,12 @@ func packagesAndErrors(args []string) []*Package {
 	args = importPaths(args)
 	var pkgs []*Package
 	var stk importStack
+	var set = make(map[string]bool)
+
 	for _, arg := range args {
+		set[arg] = true
+	}
+	for arg := range set {
 		pkgs = append(pkgs, loadPackage(arg, &stk))
 	}
 

src/cmd/go/test.bash

--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -136,6 +136,12 @@ if GOPATH=:$(pwd)/testdata:. ./testgo build go-cmd-test; then
     ok=false
 fi
 
+# issue 4104
+if [ $(./testgo test fmt fmt fmt fmt fmt | wc -l) -ne 1 ] ; then
+    echo 'go test fmt fmt fmt fmt fmt tested the same package multiple times'
+    ok=false
+fi
+
 if $ok; then
 	echo PASS
 else

コアとなるコードの解説

src/cmd/go/pkg.goの変更

packagesAndErrors関数は、goコマンドが受け取った引数(パッケージパスなど)を処理し、実際にビルドやテストの対象となる*Package型のスライスを返す役割を担っています。

変更前は、argsスライスを直接ループしてloadPackageを呼び出していました。これにより、argsに同じパッケージが複数回含まれていると、そのパッケージが複数回ロードされる可能性がありました。

変更後、setというmap[string]boolが導入されました。

  1. 最初のforループfor _, arg := range argsでは、入力されたargsスライスから各引数argを取り出し、それをsetのキーとして設定しています。マップのキーは一意であるため、これによりargsに含まれる重複したパッケージ名が自動的に排除され、setには一意のパッケージ名のみが格納されます。
  2. 次のforループfor arg := range setでは、setに格納された一意のパッケージ名のみをイテレートします。そして、それぞれのargに対してloadPackage関数を呼び出し、結果をpkgsスライスに追加します。

この二段階の処理により、goコマンドは引数として与えられたパッケージのリストから重複を効率的に排除し、必要なパッケージのみを一度だけロードして処理するようになります。これは、特に多数のパッケージを扱う場合や、スクリプトなどで誤って同じパッケージを複数回指定してしまった場合に、パフォーマンスの向上とリソースの節約に貢献します。

src/cmd/go/test.bashの変更

このファイルは、goコマンドの動作を検証するためのシェルスクリプトによるテストケースを含んでいます。

追加されたテストブロックは、Go issue #4104で報告された問題を具体的に検証するためのものです。 ./testgo test fmt fmt fmt fmt fmtコマンドは、fmtパッケージのテストを5回連続で実行するように見えます。 wc -lコマンドは、その出力の行数をカウントします。

  • 変更前: 重複排除が機能していない場合、fmtパッケージのテストが5回実行され、その結果が複数行(例えば、各テスト実行ごとに1行の出力がある場合)になることが予想されます。
  • 変更後: 重複排除が機能していれば、fmtパッケージのテストは一度だけ実行され、その出力は1行になるはずです。

if [ $(...) -ne 1 ]という条件は、「コマンドの出力行数が1ではない場合」をチェックしています。もし1でなければ、echoでエラーメッセージを出力し、ok変数をfalseに設定してテスト失敗を示します。

このテストケースは、このコミットによって導入された重複排除ロジックが期待通りに機能していることを自動的に検証するための重要な役割を果たします。

関連リンク

  • Go issue #4104: https://github.com/golang/go/issues/4104 (このコミットが修正した問題のトラッカー)
  • Gerrit Change-Id: I211211211211211211211211211211211211211 (GoプロジェクトのコードレビューシステムGerritにおけるこの変更のID。コミットメッセージのhttps://golang.org/cl/6639051に対応)

参考にした情報源リンク

  • Go issue #4104のGitHubページ
  • Go言語の公式ドキュメント(パッケージ、マップ、スライス、goコマンドに関する情報)
  • Go言語のソースコード(src/cmd/go/pkg.goおよびsrc/cmd/go/test.bash
  • wcコマンドのmanページ(wc -lの動作確認のため)
  • シェルスクリプトの条件分岐に関する一般的な知識