[インデックス 10310] ファイルの概要
このコミットは、Goコンパイラ(gc
)にGOEXPERIMENT=reorg
という実験的なフラグを追加するものです。このフラグは、Goの標準ライブラリのパッケージパスが再編成された際に、古いパッケージパスから新しいパッケージパスへの変換を一時的に容易にすることを目的としています。
コミット
commit ae7a003cf9be6822419d108d6ca1cefa86bef488
Author: Russ Cox <rsc@golang.org>
Date: Wed Nov 9 12:35:45 2011 -0500
gc: add GOEXPERIMENT=reorg
This won't last long but may ease conversions.
R=ken2
CC=golang-dev
https://golang.org/cl/5375043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ae7a003cf9be6822419d108d6ca1cefa86bef488
元コミット内容
gc: add GOEXPERIMENT=reorg
This won't last long but may ease conversions.
変更の背景
このコミットは、Go言語の初期段階における標準ライブラリのパッケージ構造の再編成(reorganization)に関連しています。Go言語は開発の初期段階で、より良い構造と命名規則を求めて、既存のパッケージのパスを変更することがありました。例えば、json
パッケージがencoding/json
に、http
パッケージがnet/http
に移動するといった変更です。
このような大規模なパッケージパスの変更は、既存のコードベースに大きな影響を与え、開発者がコードを新しいパスに合わせて手動で修正する必要がありました。このコミットで導入されたGOEXPERIMENT=reorg
フラグは、この移行期間中の開発者の負担を軽減するための一時的なメカニズムとして導入されました。このフラグを有効にすることで、コンパイラが古いパッケージパスを自動的に新しいパスにマッピングし、コードの修正なしにコンパイルを可能にしました。コミットメッセージにある「This won't last long but may ease conversions.」という記述は、この機能が一時的なものであり、移行期間が過ぎれば削除されることを示唆しています。
前提知識の解説
Goコンパイラ (gc
)
gc
は、Go言語の公式コンパイラであり、Goのソースコードを機械語に変換する役割を担っています。Goのツールチェインの一部として、ビルドプロセスの中核を成します。
GOEXPERIMENT
環境変数
GOEXPERIMENT
は、Goのツールチェインにおける実験的な機能を有効にするための環境変数です。Goの開発チームは、新しい機能や変更を導入する前に、この環境変数を使ってそれらをテストし、フィードバックを収集することがあります。GOEXPERIMENT
で有効化された機能は、安定版リリースには含まれない可能性があり、将来的に変更または削除されることがあります。
Goのパッケージパスとインポート
Go言語では、パッケージはファイルシステム上のディレクトリ構造に対応しています。import
ステートメントは、他のパッケージの機能を利用するために使用され、その際にパッケージのパスを指定します。例えば、import "fmt"
は標準ライブラリのfmt
パッケージをインポートします。パッケージパスの変更は、import
ステートメントの変更を意味し、大規模なコードベースでは手動での修正が困難になることがあります。
src/cmd/gc/go.h
とsrc/cmd/gc/lex.c
src/cmd/gc/go.h
: Goコンパイラgc
のグローバルな定義や外部変数宣言が含まれるヘッダーファイルです。新しい実験的なフラグの状態を保持するための変数がここに宣言されます。src/cmd/gc/lex.c
: Goコンパイラの字句解析(lexical analysis)を担当する部分のソースコードです。字句解析は、ソースコードをトークン(キーワード、識別子、演算子など)に分解するプロセスです。このファイルでは、GOEXPERIMENT
環境変数の処理や、インポートパスの解決ロジックが変更されています。
技術的詳細
このコミットの技術的な核心は、GOEXPERIMENT=reorg
フラグが有効な場合に、Goコンパイラが特定の古いインポートパスを新しいインポートパスに自動的に書き換えるメカニズムにあります。
reorg
変数の導入:src/cmd/gc/go.h
にEXTERN int reorg;
が追加され、reorg
というグローバル変数が宣言されます。この変数は、GOEXPERIMENT=reorg
が設定されているかどうかを示すフラグとして機能します。GOEXPERIMENT
の処理:src/cmd/gc/lex.c
内のexper
配列に{"reorg", &reorg}
が追加されます。これにより、コンパイラの起動時にGOEXPERIMENT=reorg
が指定された場合、reorg
変数の値が1
に設定されます。reorgpath
関数の追加:src/cmd/gc/lex.c
にreorgpath
という新しい関数が追加されます。この関数は、与えられた文字列リテラル(インポートパス)をチェックし、reorgtab
というマッピングテーブルに基づいて、古いパスを新しいパスに変換します。reorgtab
マッピングテーブル:reorgtab
は、古いパッケージパスと新しいパッケージパスのペアを定義した静的な構造体の配列です。例えば、{"asn1", "encoding/asn1"}
は、古いasn1
パッケージがencoding/asn1
に移動したことを示します。- インポートパスの変換ロジック:
src/cmd/gc/lex.c
内のimportfile
関数(インポートパスを処理する部分)に、if(reorg) f->u.sval = reorgpath(f->u.sval);
という行が追加されます。これは、reorg
フラグが有効な場合に、インポートされるファイルのパスがreorgpath
関数によって変換されることを意味します。
この仕組みにより、開発者はGOEXPERIMENT=reorg
を設定するだけで、古いインポートパスを使用している既存のコードを、手動で修正することなく新しいパッケージ構造でコンパイルできるようになりました。これは、大規模なコードベースの移行を円滑に進めるための一時的な互換性レイヤーとして機能しました。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -852,6 +852,7 @@ EXTERN int typecheckok;
EXTERN int compiling_runtime;
EXTERN int rune32;
+EXTERN int reorg;
/*
* y.tab.c
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -18,6 +18,8 @@ int windows;
int yyprev;
int yylast;
+Strlit *reorgpath(Strlit*);
+
static void lexinit(void);
static void lexinit1(void);
static void lexfini(void);
@@ -38,6 +40,7 @@ static struct {
int *val;
} exper[] = {
{"rune32", &rune32},
+ {"reorg", &reorg},
};
static void
@@ -537,6 +540,9 @@ importfile(Val *f, int line)
yyerror("import path contains NUL");
errorexit();
}
+
+ if(reorg)
+ f->u.sval = reorgpath(f->u.sval);
// The package name main is no longer reserved,
// but we reserve the import path "main" to identify
@@ -2115,3 +2121,47 @@ mkpackage(char* pkgname)
outfile = smprint("%s.%c", namebuf, thechar);
}\n}\n+\n+static struct {\n+\tchar *old;\n+\tchar *xnew;\n+} reorgtab[] = {\n+\t{\"asn1\", \"encoding/asn1\"},\n+\t{\"big\", \"math/big\"},\n+\t{\"cmath\", \"math/cmplx\"},\n+\t{\"csv\", \"encoding/csv\"},\n+\t{\"exec\", \"os/exec\"},\n+\t{\"exp/template/html\", \"html/template\"},\n+\t{\"gob\", \"encoding/gob\"},\n+\t{\"http\", \"net/http\"},\n+\t{\"http/cgi\", \"net/http/cgi\"},\n+\t{\"http/fcgi\", \"net/http/fcgi\"},\n+\t{\"http/httptest\", \"net/http/httptest\"},\n+\t{\"http/pprof\", \"net/http/pprof\"},\n+\t{\"json\", \"encoding/json\"},\n+\t{\"mail\", \"net/mail\"},\n+\t{\"rpc\", \"net/rpc\"},\n+\t{\"rpc/jsonrpc\", \"net/rpc/jsonrpc\"},\n+\t{\"scanner\", \"text/scanner\"},\n+\t{\"smtp\", \"net/smtp\"},\n+\t{\"syslog\", \"log/syslog\"},\n+\t{\"tabwriter\", \"text/tabwriter\"},\n+\t{\"template\", \"text/template\"},\n+\t{\"template/parse\", \"text/template/parse\"},\n+\t{\"rand\", \"math/rand\"},\n+\t{\"url\", \"net/url\"},\n+\t{\"utf16\", \"unicode/utf16\"},\n+\t{\"utf8\", \"unicode/utf8\"},\n+\t{\"xml\", \"encoding/xml\"},\n+};\n+\n+Strlit*\n+reorgpath(Strlit *s)\n+{\n+\tint i;\n+\n+\tfor(i=0; i < nelem(reorgtab); i++)\n+\t\tif(strcmp(s->s, reorgtab[i].old) == 0)\n+\t\t\treturn strlit(reorgtab[i].xnew);\n+\treturn s;\n+}\n```
## コアとなるコードの解説
### `src/cmd/gc/go.h`の変更
* `EXTERN int reorg;`: `reorg`という名前の整数型外部変数を宣言しています。`EXTERN`キーワードは、この変数が他のファイルで定義されていることを示し、このファイルではその宣言のみを行うことを意味します。この変数は、`GOEXPERIMENT=reorg`が有効かどうかをコンパイラ全体で共有するためのフラグとして使用されます。
### `src/cmd/gc/lex.c`の変更
1. **`Strlit *reorgpath(Strlit*);`の追加**: `reorgpath`関数の前方宣言です。この関数が文字列リテラル(`Strlit`型)を受け取り、変換された文字列リテラルを返すことをコンパイラに伝えます。
2. **`exper`配列への`{"reorg", &reorg}`の追加**:
* `exper`配列は、`GOEXPERIMENT`環境変数で指定できる実験的なフラグとそのフラグに対応する変数のポインタをマッピングしています。
* `{"reorg", &reorg}`を追加することで、`GOEXPERIMENT=reorg`が設定された場合に、`reorg`変数が`1`に設定されるようになります。これにより、コンパイラは`reorg`フラグが有効であることを認識できます。
3. **`importfile`関数内の変更**:
* `if(reorg) f->u.sval = reorgpath(f->u.sval);`: この行が、インポートパスの変換ロジックのトリガーです。
* `importfile`関数は、Goソースコード内の`import`ステートメントによって指定されたパスを処理します。
* `reorg`変数が`true`(つまり`GOEXPERIMENT=reorg`が有効)の場合、インポートパスを表す文字列リテラル`f->u.sval`が`reorgpath`関数に渡され、その戻り値(変換された新しいパス、または変換されなかった元のパス)で更新されます。
4. **`reorgtab`構造体配列の追加**:
* `static struct { char *old; char *xnew; } reorgtab[] = { ... };`: これは、古いパッケージパスと新しいパッケージパスの対応関係を定義する静的なテーブルです。
* 各エントリは、`old`(古いパス)と`xnew`(新しいパス)のペアで構成されています。例えば、`{"json", "encoding/json"}`は、`json`パッケージが`encoding/json`に移動したことを示します。
5. **`reorgpath`関数の実装**:
* `Strlit* reorgpath(Strlit *s)`: この関数は、引数として渡された文字列リテラル`s`(インポートパス)を`reorgtab`テーブルと照合します。
* `for(i=0; i < nelem(reorgtab); i++)`: `reorgtab`の全エントリをループで回ります。
* `if(strcmp(s->s, reorgtab[i].old) == 0)`: 現在のインポートパス`s->s`が`reorgtab`内の`old`パスと一致するかどうかを比較します。
* `return strlit(reorgtab[i].xnew);`: 一致するエントリが見つかった場合、対応する`xnew`パスを表す新しい文字列リテラルを返します。
* `return s;`: 一致するエントリが見つからなかった場合、元の文字列リテラル`s`をそのまま返します。
これらの変更により、Goコンパイラは、`GOEXPERIMENT=reorg`が設定されている場合に、古いインポートパスを自動的に新しいパスに変換する能力を獲得しました。これにより、Goの標準ライブラリのパッケージ再編成に伴うコードの修正作業が大幅に簡素化されました。
## 関連リンク
* GitHubコミットページ: [https://github.com/golang/go/commit/ae7a003cf9be6822419d108d6ca1cefa86bef488](https://github.com/golang/go/commit/ae7a003cf9be6822419d108d6ca1cefa86bef488)
* Gerrit Code Review (golang.org): [https://golang.org/cl/5375043](https://golang.org/cl/5375043)
## 参考にした情報源リンク
* Go言語の`GOEXPERIMENT`環境変数に関する情報 (Web検索結果より)
* [https://go.dev/doc/go1.18#goexperiment](https://go.dev/doc/go1.18#goexperiment) (これはGo 1.18のドキュメントですが、`GOEXPERIMENT`の概念を理解するのに役立ちます)
* [https://gochanges.org/](https://gochanges.org/) (Goの変更履歴を追跡するサイト)
* Goのパッケージ構造とインポートに関する一般的な情報
* C言語の`strcmp`関数、構造体、配列に関する一般的なプログラミング知識