[インデックス 13875] ファイルの概要
このコミットは、Go言語のmisc/cgo/stdio
パッケージにおける標準入出力(stdout
およびstderr
)の扱いを改善し、特にWindows環境での互換性を確保するとともに、関連するテストの堅牢性を向上させるものです。具体的には、C言語の標準ライブラリ(libc)の実装に依存せずにstdout
とstderr
を取得する汎用的な方法を導入し、テストスクリプトが異なるOSの改行コードの違いを吸収できるように修正しています。
コミット
commit 674bbafce6a52ef843eb130200d2946c92d9934d
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Sep 20 00:27:23 2012 +0800
misc/cgo/stdio: make it work on Windows and also test it
use a function to get stdout and stderr, instead of depending
on a specific libc implementation.
also make test/run.go replace \r\n by \n before comparing
output.
Fixes #2121.
Part of issue 1741.
R=alex.brainman, rsc, r, remyoudompheng
CC=golang-dev
https://golang.org/cl/5847068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/674bbafce6a52ef843eb130200d2946c92d9934d
元コミット内容
misc/cgo/stdio: make it work on Windows and also test it
use a function to get stdout and stderr, instead of depending
on a specific libc implementation.
also make test/run.go replace \r\n by \n before comparing
output.
Fixes #2121.
Part of issue 1741.
変更の背景
このコミットは、主に以下の2つの問題に対処するために行われました。
-
Windows環境での
stdout
/stderr
の取得問題 (Fixes #2121): Go言語のmisc/cgo/stdio
パッケージは、C言語の標準入出力ストリーム(stdout
,stderr
)をGoコードから利用するためのラッパーを提供しています。しかし、これらのストリームへのアクセス方法が、使用されるC標準ライブラリ(libc)の実装によって異なることが問題となっていました。特にWindows環境(MinGWなど)では、stdout
やstderr
が_iob
のような内部構造体のアドレスとして定義されていることがあり、GoのCgoがこれを直接認識できない、またはプラットフォーム間で互換性のない方法でアクセスしようとすると問題が発生していました。NetBSDのような他のOSでも同様の問題(__sF
構造体への依存)が存在していました。このため、特定のlibc実装に依存しない、よりポータブルな方法でstdout
とstderr
を取得する必要がありました。 -
クロスプラットフォームなテストにおける改行コードの問題: Windowsではテキストファイルの改行コードとしてCRLF(
\r\n
)が一般的に使用されますが、Unix系システム(Linux, macOSなど)ではLF(\n
)が使用されます。Goのテストフレームワーク(特にtest/run.go
)がプログラムの出力を比較する際、この改行コードの違いが原因でテストが失敗することがありました。例えば、テスト対象のプログラムがWindowsで実行されてCRLFを含む出力を生成した場合、期待される出力がLFのみで記述されていると、比較時に不一致と判断されてしまいます。この問題を解決し、テストの信頼性を向上させる必要がありました。
これらの問題は、Go言語が多様なプラットフォームで動作することを目標としている中で、特にCgoのようなOS固有の機能と連携する部分で顕在化する典型的なクロスプラットフォーム互換性の課題でした。
前提知識の解説
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Goのソースファイル内にC言語のコードを直接記述したり、既存のCライブラリをリンクしたりすることができます。Cgoを使用すると、Goの型とCの型の間でデータの変換が行われ、GoのガベージコレクタとCのメモリ管理の間で連携が取られます。
Cgoの基本的な仕組みは以下の通りです。
- Goのソースファイル内に
import "C"
という行を記述し、その直前のコメントブロックにC言語のコードを記述します。 - Cの関数や変数にアクセスする際は、
C.
プレフィックスを付けて参照します(例:C.printf
,C.stdout
)。 - CgoツールがGoとCの間のバインディングコードを生成し、GoコンパイラとCコンパイラ(通常はGCCやClang)が連携して最終的なバイナリを生成します。
stdout
とstderr
stdout
(標準出力)とstderr
(標準エラー出力)は、C言語の標準ライブラリstdio.h
で定義されているFILE
型のポインタです。これらは、プログラムがテキストを出力するためのストリームを表します。
stdout
: 通常のプログラム出力に使用されます。stderr
: エラーメッセージや診断情報に使用されます。
これらのポインタは、通常、プログラムの起動時にOSによって開かれ、それぞれコンソールやファイルに紐付けられます。しかし、これらのポインタがどのように実装されているかは、C標準ライブラリ(libc)の実装(例: glibc, musl, MSVCRT, NetBSDのlibcなど)によって異なります。
_iob
: Microsoft Visual C++ Runtime (MSVCRT) やMinGWなどのWindows環境のCライブラリで、FILE
構造体の配列を管理するために使われる内部的なシンボル名です。stdin
,stdout
,stderr
は、この_iob
配列の特定のエントリを指すポインタとして定義されることがあります。__sF
: NetBSDのCライブラリで、同様にFILE
構造体の配列を管理するために使われる内部的なシンボル名です。
Cgoがこれらの内部的なシンボルを直接参照しようとすると、異なるOSやコンパイラ環境でシンボル名や構造体のレイアウトが異なるため、コンパイルエラーや実行時エラーが発生する可能性があります。
改行コード (CRLF vs LF)
テキストファイルにおける改行の表現方法は、オペレーティングシステムによって異なります。
- LF (Line Feed,
\n
, ASCII 10): Unix系システム(Linux, macOS, BSDなど)で標準的に使用される改行コードです。 - CRLF (Carriage Return + Line Feed,
\r\n
, ASCII 13 + ASCII 10): Windowsシステムで標準的に使用される改行コードです。CRはカーソルを行頭に戻し、LFはカーソルを次の行に進めるという、タイプライターの動作に由来します。
プログラムがテキスト出力を生成する際、OSの慣習に従って改行コードを付加することが一般的です。しかし、異なるOSで生成された出力を比較する場合、この改行コードの違いが問題となることがあります。特に自動テストでは、期待される出力と実際の出力がバイト単位で完全に一致することを要求する場合があるため、改行コードの正規化が必要になります。
技術的詳細
このコミットの技術的解決策は、上記の問題に直接対処しています。
-
stdout
/stderr
の取得方法の抽象化:misc/cgo/stdio/stdio.go
ファイルにおいて、C言語のstdout
およびstderr
グローバル変数への直接参照を避け、代わりにCgo経由で呼び出せるC関数getStdout()
とgetStderr()
を導入しました。- 以前は
var Stdout = (*File)(C.stdout)
のように直接C.stdout
を参照していました。 - 新しいアプローチでは、Goコードから
C.getStdout()
とC.getStderr()
を呼び出し、これらのC関数が内部でstdout
とstderr
のポインタを返します。 - この変更により、Cgoは特定のlibc実装の内部構造(
_iob
や__sF
など)を直接「知る」必要がなくなり、Cコンパイラが提供する標準的なstdout
/stderr
シンボルを介してアクセスできるようになります。これにより、Windows(MinGW)やNetBSDなど、様々な環境での互換性が向上します。 misc/cgo/stdio/stdio_netbsd.go
ファイルは、NetBSD固有の__sF
への依存を解消するために削除されました。これは、新しい汎用的なアプローチがNetBSDでも機能するため、特定のOS向けのコードが不要になったことを意味します。
- 以前は
-
テストにおける改行コードの正規化:
test/run.go
ファイルにおいて、テスト対象プログラムの出力と期待される出力を比較する前に、実際の出力からCRLF(\r\n
)をLF(\n
)に置換する処理を追加しました。- 具体的には、
strings.Replace(string(out), "\r\n", "\n", -1)
というGoの標準ライブラリ関数を使用しています。 - これにより、Windows環境で生成された出力がCRLFを含んでいても、比較時にはLFのみの形式に正規化されるため、期待される出力(通常はLFで記述されている)との比較が正確に行えるようになります。
- この変更は、
src/run.bat
(Windows用のテスト実行スクリプト)にも反映され、misc/cgo/stdio
のテストが有効化されました。以前はコメントアウトされていた行が有効化され、このモジュールのテストがCI/CDパイプラインで実行されるようになりました。
- 具体的には、
これらの変更は、Go言語のクロスプラットフォーム対応を強化し、特にCgoのような低レベルな連携が必要な部分での堅牢性を高める上で重要なステップです。
コアとなるコードの変更箇所
misc/cgo/stdio/stdio.go
--- a/misc/cgo/stdio/stdio.go
+++ b/misc/cgo/stdio/stdio.go
@@ -1,15 +1,22 @@
+// skip
+
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build !netbsd
-
package stdio
/*
#include <stdio.h>
+\n
+// on mingw, stderr and stdout are defined as &_iob[FILENO]
+// on netbsd, they are defined as &__sF[FILENO]
+// and cgo doesn't recognize them, so write a function to get them,
+// instead of depending on internals of libc implementation.
+FILE *getStdout(void) { return stdout; }
+FILE *getStderr(void) { return stderr; }
*/
import "C"
-var Stdout = (*File)(C.stdout)
-var Stderr = (*File)(C.stderr)
+var Stdout = (*File)(C.getStdout())
+var Stderr = (*File)(C.getStderr())
misc/cgo/stdio/stdio_netbsd.go
このファイルは完全に削除されました。
--- a/misc/cgo/stdio/stdio_netbsd.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.\n
-package stdio
-
-/*
-#include <stdio.h>
-
-extern FILE __sF[3];
-*/
-import "C"
-import "unsafe"
-
-var Stdout = (*File)(unsafe.Pointer(&C.__sF[1]))
-var Stderr = (*File)(unsafe.Pointer(&C.__sF[2]))
src/run.bat
--- a/src/run.bat
+++ b/src/run.bat
@@ -70,11 +70,10 @@ if x%CGO_ENABLED% == x0 goto nocgo
::if errorlevel 1 goto fail
::echo.
-:: TODO ..\misc\cgo\stdio
-::echo # ..\misc\cgo\stdio
-::go run %GOROOT%\test\run.go - ..\misc\cgo\stdio
-::if errorlevel 1 goto fail
-::echo.
+echo # ..\misc\cgo\stdio
+go run %GOROOT%\test\run.go - ..\misc\cgo\stdio
+if errorlevel 1 goto fail
+echo.
echo # ..\misc\cgo\test
go test ..\misc\cgo\test
test/run.go
--- a/test/run.go
+++ b/test/run.go
@@ -344,7 +344,7 @@ func (t *test) run() {
if err != nil {
t.err = fmt.Errorf("%s\n%s", err, out)
}
- if string(out) != t.expectedOutput() {
+ if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
t.err = fmt.Errorf("incorrect output\n%s", out)
}
コアとなるコードの解説
misc/cgo/stdio/stdio.go
の変更
このファイルは、Goのstdio
パッケージがCの標準入出力ストリームにアクセスするためのインターフェースを提供します。
-
C関数の追加:
FILE *getStdout(void) { return stdout; } FILE *getStderr(void) { return stderr; }
このCコードブロックは、CgoによってGoから呼び出し可能なC関数を定義しています。
getStdout()
はCのグローバル変数stdout
のポインタを返し、getStderr()
はstderr
のポインタを返します。これにより、GoコードはCの内部実装に直接依存することなく、標準的なCのAPIを通じてこれらのポインタを取得できます。 -
Go変数の更新:
var Stdout = (*File)(C.getStdout()) var Stderr = (*File)(C.getStderr())
Goの
Stdout
とStderr
変数は、以前はC.stdout
とC.stderr
を直接キャストしていましたが、この変更により、新しく定義されたC関数C.getStdout()
とC.getStderr()
の戻り値を使用するようになりました。これにより、異なるCライブラリ実装間での互換性の問題が解消されます。 -
// skip
コメントの追加:// skip
コメントは、Goのビルドシステムに対して、このファイルが特定のビルド条件(例:+build
タグ)なしで常にコンパイルされるべきではないことを示唆している可能性があります。ただし、このコミットの文脈では、主にCgoの動作に関連する変更が重要です。
misc/cgo/stdio/stdio_netbsd.go
の削除
このファイルは、NetBSD環境でのstdout
/stderr
の取得に特化したコードを含んでいました。具体的には、NetBSDのlibcが__sF
という内部配列を使ってFILE
ポインタを管理していることに依存していました。
var Stdout = (*File)(unsafe.Pointer(&C.__sF[1]))
var Stderr = (*File)(unsafe.Pointer(&C.__sF[2]))
このようなunsafe.Pointer
を使った直接的なメモリ操作は、libcの実装詳細に強く依存するため、非常に脆弱です。stdio.go
で導入された汎用的なgetStdout()
/getStderr()
関数がNetBSDでも正しく機能するようになったため、このNetBSD固有のファイルは不要となり、削除されました。これにより、コードベースの複雑性が減少し、保守性が向上しました。
src/run.bat
の変更
run.bat
は、Windows環境でGoのテストスイートを実行するためのバッチスクリプトです。
misc/cgo/stdio
テストの有効化:
以前はコメントアウトされていた-:: TODO ..\misc\cgo\stdio -::echo # ..\misc\cgo\stdio -::go run %GOROOT%\test\run.go - ..\misc\cgo\stdio -::if errorlevel 1 goto fail -::echo. +echo # ..\misc\cgo\stdio +go run %GOROOT%\test\run.go - ..\misc\cgo\stdio +if errorlevel 1 goto fail +echo.
misc/cgo/stdio
モジュールのテスト実行コマンドが有効化されました。これは、stdio
パッケージのWindows互換性が向上したため、この環境でのテストが可能になったことを示しています。go run %GOROOT%\test\run.go - ..\misc\cgo\stdio
コマンドは、Goのテストランナーを使用してmisc/cgo/stdio
ディレクトリ内のテストを実行します。
test/run.go
の変更
test/run.go
は、Goの標準テストフレームワークの一部であり、テストの実行と結果の比較を担当します。
- 出力の改行コード正規化:
この変更は、テスト対象プログラムの標準出力(- if string(out) != t.expectedOutput() { + if strings.Replace(string(out), "\r\n", "\n", -1) != t.expectedOutput() {
out
変数)を、期待される出力(t.expectedOutput()
)と比較する直前に行われます。strings.Replace(string(out), "\r\n", "\n", -1)
は、out
バイトスライスを文字列に変換し、その文字列内のすべてのCRLF(\r\n
)シーケンスをLF(\n
)に置換します。-1
は、すべての出現箇所を置換することを意味します。 この正規化により、Windowsで生成されたCRLFを含む出力が、Unix系システムで期待されるLFのみの出力と正しく比較できるようになり、クロスプラットフォームなテストの信頼性が向上します。
これらの変更は、Go言語のCgo機能がより堅牢でクロスプラットフォーム互換性を持つようにするための重要な改善であり、テストインフラストラクチャもそれに合わせて適応されています。
関連リンク
- Go Issue #2121: https://github.com/golang/go/issues/2121
- Go Issue #1741: https://github.com/golang/go/issues/1741
- Go CL 5847068: https://golang.org/cl/5847068 (Gerrit Code Review)
参考にした情報源リンク
- Go Programming Language: https://golang.org/
- Cgo documentation: https://pkg.go.dev/cmd/cgo
stdio.h
documentation (C standard library): (一般的なC言語のドキュメントやリファレンスを参照)- Line ending conventions (CRLF vs LF): (Wikipediaや関連する技術記事を参照)
- MinGW: https://www.mingw-w64.org/
- NetBSD: https://www.netbsd.org/
strings.Replace
function in Go: https://pkg.go.dev/strings#Replace