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

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

このコミットは、Go言語のCgoツールにおいて、環境変数 CGO_LDFLAGS の値を _cgo_flags ファイルに記録するように変更するものです。これにより、外部リンキング時に CGO_LDFLAGS で指定されたリンカフラグが適切にCリンカに渡されるようになり、特にライブラリのパスやライブラリ自体が正しく解決されるようになります。

コミット

commit afb49aada5b79b360d6c16699b0b8121ae6b2b71
Author: Andrew Wilkins <axwalk@gmail.com>
Date:   Tue Apr 9 07:35:06 2013 +0800

    cmd/cgo: record CGO_LDFLAGS env var in _cgo_flags
    
    cgo stores cgo LDFLAGS in _cgo_flags and _cgo_defun.c.
    The _cgo_defun.c records the flags via
    "#pragma cgo_ldflag <flag>", which external linking
    relies upon for passing libraries (and search paths)
    to the host linker.
    
    The go command will allow LDFLAGS for cgo to be passed
    through the environment (CGO_LDFLAGS); cgo ignores
    this environment variable, and so its value doesn't
    make it into the above mentioned files. This CL changes
    cgo to record CGO_LDFLAGS also.
    
    Fixes #5205.
    
    R=iant, minux.ma
    CC=golang-dev
    https://golang.org/cl/8465043

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

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

元コミット内容

cmd/cgo: record CGO_LDFLAGS env var in _cgo_flags
    
cgo stores cgo LDFLAGS in _cgo_flags and _cgo_defun.c.
The _cgo_defun.c records the flags via
"#pragma cgo_ldflag <flag>", which external linking
relies upon for passing libraries (and search paths)
to the host linker.

The go command will allow LDFLAGS for cgo to be passed
through the environment (CGO_LDFLAGS); cgo ignores
this environment variable, and so its value doesn't
make it into the above mentioned files. This CL changes
cgo to record CGO_LDFLAGS also.

Fixes #5205.

R=iant, minux.ma
CC=golang-dev
https://golang.org/cl/8465043

変更の背景

Go言語でC言語のコードを呼び出すCgoを使用する場合、C言語のライブラリをリンクするためにリンカフラグ(LDFLAGS)が必要となることがあります。これらのフラグは、Goのソースコード内で // #cgo LDFLAGS: ... のように指定することもできますが、ビルド環境やCI/CDパイプラインなどで動的にリンカフラグを渡したい場合、環境変数 CGO_LDFLAGS を使用することが一般的です。

しかし、このコミットがなされる以前のCgoツールは、CGO_LDFLAGS 環境変数の値を適切に処理していませんでした。具体的には、Cgoはリンカフラグを _cgo_flags という内部ファイルや、_cgo_defun.c という生成されるCソースファイル内の #pragma cgo_ldflag <flag> ディレクティブを通じて記録していました。これらのファイルは、GoプログラムとCライブラリを最終的に結合する外部リンキングプロセスにおいて、ホストリンカ(通常はgccやclang)にライブラリのパスやライブラリ自体を渡すために不可欠です。

CGO_LDFLAGS がCgoによって無視されていたため、この環境変数を通じて渡された重要なリンカフラグが _cgo_flags_cgo_defun.c に反映されず、結果として外部リンキングが失敗したり、意図したライブラリがリンクされないという問題が発生していました。このコミットは、この問題を解決し、CGO_LDFLAGS の値がCgoのビルドプロセスに正しく取り込まれるようにすることを目的としています。これは、Go issue #5205 に対応するものです。

前提知識の解説

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。これにより、既存のCライブラリをGoプロジェクトで再利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。Cgoを使用すると、Goのビルドプロセス中にCgoツールがGoとCの間のブリッジコードを生成し、Cコンパイラとリンカを呼び出してCコードをコンパイル・リンクします。

CGO_LDFLAGS 環境変数

CGO_LDFLAGS は、Cgoを使用するGoプログラムをビルドする際に、Cリンカに渡す追加のリンカフラグを指定するための環境変数です。例えば、特定のディレクトリにあるライブラリをリンクしたい場合、-L/path/to/lib-lmy_library のようなフラグを CGO_LDFLAGS に設定します。これは、Goソースコード内の // #cgo LDFLAGS: ... ディレクティブと組み合わせて使用されます。

_cgo_flags_cgo_defun.c

Cgoツールは、GoとCのコードを連携させるためにいくつかの中間ファイルを生成します。

  • _cgo_flags: これはCgoが生成する内部ファイルで、Cgoに関連するリンカフラグやその他のビルド情報が格納されます。GoのビルドシステムがCリンカを呼び出す際に、このファイルの内容が利用されます。
  • _cgo_defun.c: Cgoによって生成されるCソースファイルの一つです。このファイルには、GoとCの間で関数呼び出しやデータ交換を行うためのラッパー関数や定義が含まれます。特に、#pragma cgo_ldflag <flag> ディレクティブを通じて、Cgoが収集したリンカフラグがこのファイルに埋め込まれることがあります。これは、外部リンキング時にCリンカがこれらのフラグを認識するために重要です。

外部リンキング (External Linking)

Goプログラムのビルドには、主に2つのリンキングモードがあります。

  1. 内部リンキング (Internal Linking): Goのデフォルトのリンキングモードです。Go自身のリンカ (cmd/link) がすべてのGoコードと標準ライブラリを静的にリンクし、単一の実行可能ファイルを生成します。
  2. 外部リンキング (External Linking): Cgoを使用する場合や、go build -ldflags "-linkmode 'external'" のように明示的に指定した場合に利用されます。このモードでは、GoツールチェインはGoコードをオブジェクトファイルにコンパイルした後、システムのCリンカ(例: gccclang)を呼び出して、Goのオブジェクトファイル、コンパイルされたCコード、および外部Cライブラリを結合して最終的な実行可能ファイルを生成します。Goの内部リンカは任意のCオブジェクトファイルやライブラリを完全に理解してリンクすることができないため、Cgoを使用する際には外部リンキングが必要となります。

技術的詳細

このコミットの核心は、cmd/cgo ツールが CGO_LDFLAGS 環境変数の値を読み取り、それを内部のリンカフラグリストに追加する点にあります。

従来のCgoの動作では、Goソースコード内の // #cgo LDFLAGS: ... ディレクティブで指定されたリンカフラグは _cgo_flags_cgo_defun.c に適切に記録されていました。しかし、go build コマンドが CGO_LDFLAGS 環境変数を通じてリンカフラグをCgoに渡すことを許可していたにもかかわらず、Cgoツール自体がこの環境変数を明示的に読み取って処理していなかったため、その値が最終的なリンキングプロセスに反映されませんでした。

この問題は、特に動的にライブラリのパスや名前を指定する必要がある複雑なビルド環境で顕在化しました。例えば、特定のシステムにインストールされているライブラリのバージョンに応じてリンカフラグを調整したい場合、環境変数を使うのが自然なアプローチですが、Cgoがそれを無視するため、ビルドが失敗したり、予期せぬ動作を引き起こしたりしていました。

このコミットによって、Cgoツールは os.Getenv("CGO_LDFLAGS") を呼び出して CGO_LDFLAGS 環境変数の値を取得します。取得した値は、スペースで区切られた複数のリンカフラグとして解釈され、splitQuoted 関数によって適切にパースされます。これにより、引用符で囲まれたパスやフラグも正しく処理されます。パースされたリンカフラグは、p.addToFlag("LDFLAGS", args) を通じて、Cgoが管理する内部のリンカフラグリストに追加されます。このリストは最終的に _cgo_flags ファイルや _cgo_defun.c に書き込まれ、外部リンキング時にホストリンカに渡されることになります。

この変更により、CGO_LDFLAGS 環境変数で指定されたリンカフラグが、// #cgo LDFLAGS: ディレクティブで指定されたフラグと同様に、Cgoのビルドプロセスに完全に統合されるようになり、外部リンキングの信頼性と柔軟性が向上しました。

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

--- a/src/cmd/cgo/main.go
+++ b/src/cmd/cgo/main.go
@@ -208,6 +208,15 @@ func main() {
 
 	p := newPackage(args[:i])
 
+	// Record CGO_LDFLAGS from the environment for external linking.
+	if ldflags := os.Getenv("CGO_LDFLAGS"); ldflags != "" {
+		args, err := splitQuoted(ldflags)
+		if err != nil {
+			fatalf("bad CGO_LDFLAGS: %q (%s)", ldflags, err)
+		}
+		p.addToFlag("LDFLAGS", args)
+	}
+
 	// Need a unique prefix for the global C symbols that
 	// we use to coordinate between gcc and ourselves.
 	// We already put _cgo_ at the beginning, so the main

コアとなるコードの解説

変更は src/cmd/cgo/main.go ファイルの main 関数内で行われています。

追加されたコードブロックは以下の通りです。

	// Record CGO_LDFLAGS from the environment for external linking.
	if ldflags := os.Getenv("CGO_LDFLAGS"); ldflags != "" {
		args, err := splitQuoted(ldflags)
		if err != nil {
			fatalf("bad CGO_LDFLAGS: %q (%s)", ldflags, err)
		}
		p.addToFlag("LDFLAGS", args)
	}
  1. if ldflags := os.Getenv("CGO_LDFLAGS"); ldflags != "" { ... }

    • os.Getenv("CGO_LDFLAGS") を呼び出して、環境変数 CGO_LDFLAGS の値を取得します。
    • 取得した値が空文字列でない場合(つまり、CGO_LDFLAGS が設定されている場合)に、このブロック内の処理が実行されます。
  2. args, err := splitQuoted(ldflags)

    • 取得した ldflags 文字列を splitQuoted 関数に渡します。
    • splitQuoted 関数は、スペースで区切られた文字列を個々の引数(リンカフラグ)の配列に分割します。この際、引用符で囲まれた部分(例: -L"/path with spaces")も正しく一つの引数として扱われます。
    • パース中にエラーが発生した場合、err にエラー情報が格納されます。
  3. if err != nil { fatalf("bad CGO_LDFLAGS: %q (%s)", ldflags, err) }

    • splitQuoted でエラーが発生した場合、fatalf 関数を呼び出して致命的なエラーとして処理を終了します。これにより、不正な CGO_LDFLAGS の値がビルドプロセスを妨げることを防ぎます。
  4. p.addToFlag("LDFLAGS", args)

    • p は現在のCgoパッケージを表すオブジェクトです。
    • addToFlag メソッドは、指定された種類のフラグ(ここでは "LDFLAGS")に、パースされた引数 args を追加します。
    • これにより、CGO_LDFLAGS 環境変数で指定されたすべてのリンカフラグが、Cgoの内部的なリンカフラグリストに組み込まれ、最終的に _cgo_flags_cgo_defun.c に反映されるようになります。

この変更により、CGO_LDFLAGS を通じて渡されるリンカフラグが、Cgoのビルドプロセスにおいて適切に認識され、外部リンキング時にCリンカに渡されることが保証されます。

関連リンク

参考にした情報源リンク