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

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

このコミットは、Goコンパイラ(gc)に-Iオプションのサポートを追加するものです。このオプションを使用すると、ユーザーはパッケージ(具体的には.aおよび.6ファイル)を検索するための追加のディレクトリを指定できるようになります。これにより、Goコンパイラがパッケージを解決する際の柔軟性が向上します。

コミット

commit 062d6998abfa3fd59d47ed86547783db25ab066b
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Mar 10 20:03:31 2009 -0700

    Add support for a -I option.  -I DIR searches for packages in
    DIR.
    
    R=ken,rsc
    DELTA=49  (41 added, 2 deleted, 6 changed)
    OCL=26057
    CL=26092

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

https://github.com/golang/go/commit/062d6998abfa3fd59d47ed86547783db25ab066b

元コミット内容

-Iオプションのサポートを追加しました。-I DIRは、DIR内でパッケージを検索します。

変更の背景

Go言語の初期段階において、コンパイラがパッケージを検索するメカニズムは主にGOROOT/pkgディレクトリに依存していました。しかし、開発者がプロジェクト固有のパッケージや、標準ライブラリとは異なる場所に配置されたパッケージを使用する場合、この固定された検索パスだけでは不十分でした。

このコミットは、C/C++コンパイラにおけるインクルードパスの指定(例: gcc -I/path/to/includes)と同様の機能を提供することで、この問題を解決しようとしています。-Iオプションを導入することで、ユーザーはコンパイラに対して、指定されたディレクトリもパッケージ検索の対象に含めるように指示できるようになります。これにより、ビルドシステムや開発環境の柔軟性が向上し、より複雑なプロジェクト構成に対応できるようになります。

前提知識の解説

  • Goコンパイラ (gc): Go言語の初期の公式コンパイラはgcと呼ばれていました。これはPlan 9 Cコンパイラをベースにしており、Goのソースコードを機械語に変換する役割を担っていました。現在のGoコンパイラはより統合されたツールチェーンの一部ですが、このコミットが作成された時点ではgcが主要なコンパイラでした。
  • Goパッケージ: Go言語では、コードはパッケージにまとめられます。他のパッケージの関数や型を使用するには、importステートメントを使ってそのパッケージをインポートする必要があります。コンパイラはインポートされたパッケージの定義を見つけるために、特定の検索パスをたどります。
  • .a ファイルと .6 ファイル: Go言語の初期のビルドシステムでは、コンパイルされたパッケージは主に.a(アーカイブ)ファイルまたは.6(オブジェクト)ファイルとして保存されていました。
    • .aファイルは、複数のオブジェクトファイルやその他のリソースをまとめたアーカイブファイルで、ライブラリとして機能します。
    • .6ファイルは、Goのソースコードがコンパイルされた後のオブジェクトファイルで、Goの初期のツールチェーンでは、ターゲットアーキテクチャが64ビットの場合に.6という拡張子が使われていました(例: 8は32ビットIntel、5はARMなど)。これらのファイルには、パッケージの型情報や関数定義などが含まれており、他のGoプログラムからインポートされる際にコンパイラによって参照されます。
  • GOROOT環境変数: GOROOTはGoのインストールディレクトリを指す環境変数です。Goの標準ライブラリパッケージは通常、$GOROOT/pkg以下に配置されており、コンパイラはデフォルトでこのパスを検索します。
  • access(namebuf, 0): これはC言語のシステムコールaccess()の呼び出しです。access(path, mode)は、指定されたパスpathに対して、modeで指定されたアクセス権があるかどうかをチェックします。0はF_OK(ファイルが存在するかどうか)を意味し、ファイルが存在すれば0を返し、存在しなければ-1を返します。このコミットでは、パッケージファイル(.aまたは.6)が存在するかどうかを確認するために使用されています。
  • リンクリスト: Idir構造体とlinkフィールドは、ディレクトリパスを格納するための単純なリンクリストを形成しています。これにより、複数の-Iオプションが指定された場合でも、それらのディレクトリパスを効率的に管理できます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. Idir構造体の導入: src/cmd/gc/go.hIdirという新しい構造体が定義されました。

    typedef struct Idir Idir;
    struct Idir
    {
        Idir* link; // 次のディレクトリへのポインタ
        char* dir;  // ディレクトリパス文字列へのポインタ
    };
    

    この構造体は、-Iオプションで指定された各ディレクトリパスを格納するためのノードとして機能します。linkフィールドはリンクリストを構築するために使用され、dirフィールドは実際のディレクトリパスを保持します。

  2. グローバルリンクリストidirsの追加: src/cmd/gc/go.hEXTERN Idir* idirs;が追加されました。これは、-Iオプションで指定されたすべてのディレクトリパスを格納するリンクリストのヘッドポインタとなるグローバル変数です。

  3. addidir関数の追加: src/cmd/gc/go.hにプロトタイプが、src/cmd/gc/lex.cに実装が追加されました。

    void
    addidir(char* dir)
    {
        Idir** pp;
    
        if(dir == nil)
            return;
    
        for(pp = &idirs; *pp != nil; pp = &(*pp)->link)
            ;
        *pp = mal(sizeof(Idir));
        (*pp)->link = nil;
        (*pp)->dir = dir;
    }
    

    この関数は、引数として受け取ったディレクトリパスdirを新しいIdirノードに格納し、それをグローバルなidirsリンクリストの末尾に追加します。malはメモリ割り当て関数です。

  4. mainlexでの-Iオプションの処理: src/cmd/gc/lex.cmainlex関数(コマンドライン引数パーサー)に、-Iオプションを処理するロジックが追加されました。

    case 'I':
        addidir(ARGF());
        break;
    

    -Iオプションが検出されると、その直後の引数(ディレクトリパス)をARGF()で取得し、addidir関数に渡してリンクリストに追加します。

  5. findpkg関数におけるパッケージ検索ロジックの変更: src/cmd/gc/lex.cfindpkg関数は、指定されたパッケージ名に対応するパッケージファイル(.aまたは.6)を検索する役割を担っています。この関数が大幅に修正されました。

    • 優先順位の変更: 以前は、現在のディレクトリ、次にGOROOT/pkgの順で検索していました。変更後、まずidirsリンクリストに登録されたすべてのディレクトリを順番に検索するようになりました。
    • GOROOTの扱い: GOROOTが設定されていない場合にfindpkgが即座に0を返すロジックが削除され、GOROOTの検索はif(goroot != nil)ブロック内に移動しました。これにより、-Iで指定されたパスがあれば、GOROOTが設定されていなくてもパッケージ検索が試行されるようになりました。
    • 検索パスの構築: 各ディレクトリに対して、パッケージ名と.aまたは.6拡張子を結合したパス(例: /path/to/dir/packagename.a)をsnprintで構築し、access関数でファイルの存在を確認します。

これらの変更により、Goコンパイラは-Iオプションで指定されたパスを優先的に検索し、パッケージの解決を行うようになります。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -455,6 +455,13 @@ struct	Dlist
 	Type*	field;
 };
 
+typedef	struct	Idir	Idir;
+struct Idir
+{
+	Idir*	link;
+	char*	dir;
+};
+
 EXTERN	Dlist	dotlist[10];	// size is max depth of embeddeds
 
 EXTERN	Io	curio;
@@ -482,6 +489,7 @@ EXTERN	int	tptr;		// either TPTR32 or TPTR64
 extern	char*	sysimport;
 extern	char*	unsafeimport;
 EXTERN	char*	filename;	// name to uniqify names
+EXTERN	Idir*	idirs;
 
 EXTERN	Type*	types[NTYPE];
 EXTERN	uchar	simtype[NTYPE];
@@ -552,6 +560,7 @@ int	yyparse(void);\
  */
 int	mainlex(int, char*[]);
 void	setfilename(char*);
+void	addidir(char*);\
 void	importfile(Val*);\
 void	cannedimports(char*, char*);\
 void	unimportfile();

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -35,6 +35,10 @@ mainlex(int argc, char *argv[])
 	case 'k':
 		package = ARGF();
 		break;
+\
+\tcase 'I':
+\t\taddidir(ARGF());
+\t\tbreak;
 	} ARGEND
 
 	if(argc != 1)
@@ -109,6 +113,7 @@ mainlex(int argc, char *argv[])
 
 usage:
 	print("flags:\n");
+\tprint("  -I DIR search for packages in DIR\n");
 	print("  -d print declarations\n");
 	print("  -f print stack frame structure\n");
 	print("  -k name specify package name\n");
@@ -175,15 +180,29 @@ skiptopkgdef(Biobuf *b)\
 	return 1;\
 }\
 
+void
+addidir(char* dir)\
+{\
+\tIdir** pp;\
+\
+\tif(dir == nil)\
+\t\treturn;\
+\
+\tfor(pp = &idirs; *pp != nil; pp = &(*pp)->link)\
+\t\t;\
+\t*pp = mal(sizeof(Idir));\
+\t(*pp)->link = nil;\
+\t(*pp)->dir = dir;\
+}\
+\
 int
 findpkg(String *name)\
 {\
 	static char* goroot;\
+\tIdir* p;\
 \
 \tif(goroot == nil) {\
 \t\tgoroot = getenv("GOROOT");
-\t\tif(goroot == nil)
-\t\t\treturn 0;
 \t}\
 \
 \t// BOTCH need to get .6 from backend
@@ -191,18 +210,29 @@ findpkg(String *name)\
 \t// try .a before .6.  important for building libraries:
 \t// if there is an array.6 in the array.a library,
 \t// want to find all of array.a, not just array.6.
+\tfor(p = idirs; p != nil; p = p->link) {\
+\t\tsnprint(namebuf, sizeof(namebuf), "%s/%Z.a", p->dir, name);\
+\t\tif(access(namebuf, 0) >= 0)\
+\t\t\treturn 1;\
+\t\tsnprint(namebuf, sizeof(namebuf), "%s/%Z.6", p->dir, name);\
+\t\tif(access(namebuf, 0) >= 0)\
+\t\t\treturn 1;\
+\t}\
+\
 \tsnprint(namebuf, sizeof(namebuf), "%Z.a", name);\
 \tif(access(namebuf, 0) >= 0)\
 \t\treturn 1;\
 \tsnprint(namebuf, sizeof(namebuf), "%Z.6", name);\
 \tif(access(namebuf, 0) >= 0)\
 \t\treturn 1;\
-\tsnprint(namebuf, sizeof(namebuf), "%s/pkg/%Z.a", goroot, name);\
-\tif(access(namebuf, 0) >= 0)\
-\t\treturn 1;\
-\tsnprint(namebuf, sizeof(namebuf), "%s/pkg/%Z.6", goroot, name);\
-\tif(access(namebuf, 0) >= 0)\
-\t\treturn 1;\
+\tif(goroot != nil) {\
+\t\tsnprint(namebuf, sizeof(namebuf), "%s/pkg/%Z.a", goroot, name);\
+\t\tif(access(namebuf, 0) >= 0)\
+\t\t\treturn 1;\
+\t\tsnprint(namebuf, sizeof(namebuf), "%s/pkg/%Z.6", goroot, name);\
+\t\tif(access(namebuf, 0) >= 0)\
+\t\t\treturn 1;\
+\t}\
 \treturn 0;\
 }\
 

コアとなるコードの解説

  • src/cmd/gc/go.h:

    • Idir構造体は、-Iオプションで指定される各ディレクトリパスを保持するためのデータ構造を定義しています。linkはリンクリストの次の要素へのポインタ、dirはディレクトリパス文字列へのポインタです。
    • EXTERN Idir* idirs;は、Idir構造体のリンクリストの先頭を指すグローバル変数idirsを宣言しています。この変数は、コンパイラがパッケージを検索する追加のディレクトリパスを管理するために使用されます。
    • void addidir(char*);は、新しいディレクトリパスをidirsリンクリストに追加するための関数のプロトタイプです。
  • src/cmd/gc/lex.c:

    • mainlex関数内のcase 'I':ブロックは、コマンドライン引数から-Iオプションを解析し、その引数(ディレクトリパス)をaddidir関数に渡してidirsリンクリストに追加します。
    • usageメッセージに-Iオプションの説明が追加され、ユーザーがこのオプションの存在と目的を理解できるようになりました。
    • addidir関数は、与えられたディレクトリパスを新しいIdirノードとしてidirsリンクリストの末尾に動的に追加します。これにより、複数の-Iオプションが指定されても、すべてのパスが適切に処理されます。
    • findpkg関数は、Goコンパイラのパッケージ検索ロジックの核心です。このコミットにより、findpkgはまずidirsリンクリスト内のすべてのディレクトリを順番に検索し、指定されたパッケージ名に対応する.aまたは.6ファイルが存在するかどうかを確認します。この検索が失敗した場合にのみ、現在のディレクトリ、そしてGOROOT/pkgディレクトリを検索するようになります。これにより、ユーザーが指定したパスが最も高い優先順位で検索されるようになり、パッケージの解決がより柔軟に行えるようになりました。また、GOROOTが設定されていない場合でも、-Iで指定されたパスがあれば検索が続行されるように変更されています。

関連リンク

  • Go言語の初期のコンパイラツールチェーンに関する情報: https://go.dev/doc/go1.0#tool (Go 1.0のドキュメントですが、当時のツールチェーンの概念を理解するのに役立ちます)
  • Go言語のパッケージとモジュールの概念: https://go.dev/doc/code

参考にした情報源リンク