Ldflagsを使用したGoアプリケーションのバージョン情報の設定
序章
アプリケーションを本番環境にデプロイする場合、バージョン情報やその他のメタデータを使用してバイナリをビルドすると、ビルドを長期にわたって追跡するのに役立つ識別情報が追加されるため、監視、ロギング、およびデバッグのプロセスが改善されます。 このバージョン情報には、ビルド時間、バイナリをビルドするマシンまたはユーザー、ビルド対象のバージョン管理システム(VCS)コミットIDなどの非常に動的なデータが含まれることがよくあります。 これらの値は絶えず変化するため、このデータをソースコードに直接コーディングし、新しいビルドが発生する前に変更すると、面倒でエラーが発生しやすくなります。ソースファイルは移動でき、変数/定数は開発中にファイルを切り替える可能性があります。ビルドプロセスを中断します。
Goでこれを解決する1つの方法は、-ldflags
とgo build
コマンドを使用して、ソースコードを変更せずに、ビルド時に動的情報をバイナリに挿入することです。 このフラグでは、ld
はlinker を表します。これは、コンパイルされたソースコードのさまざまな部分を最終的なバイナリにリンクするプログラムです。 ldflags
は、リンカーフラグを表します。 これは、基になるGoツールチェーンリンカー cmd / link にフラグを渡すため、これと呼ばれます。これにより、ビルド時にコマンドラインからインポートされたパッケージの値を変更できます。
このチュートリアルでは、-ldflags
を使用して、ビルド時に変数の値を変更し、バージョン情報を画面に出力するサンプルアプリケーションを使用して、独自の動的情報をバイナリに導入します。
前提条件
この記事の例に従うには、次のものが必要です。
- Goのインストール方法とローカルプログラミング環境のセットアップに従ってセットアップされたGoワークスペース。
サンプルアプリケーションの構築
ldflags
を使用して動的データを導入する前に、まず情報を挿入するアプリケーションが必要です。 このステップでは、このアプリケーションを作成します。このアプリケーションは、この段階では静的なバージョン情報のみを出力します。 それでは、そのアプリケーションを作成しましょう。
src
ディレクトリに、アプリケーションにちなんで名付けられたディレクトリを作成します。 このチュートリアルでは、アプリケーション名app
を使用します。
mkdir app
作業ディレクトリを次のフォルダに変更します。
cd app
次に、選択したテキストエディタを使用して、プログラムのエントリポイントmain.go
を作成します。
nano main.go
次に、次のコンテンツを追加して、アプリケーションにバージョン情報を印刷させます。
app / main.go
package main import ( "fmt" ) var Version = "development" func main() { fmt.Println("Version:\t", Version) }
main()
関数内で、Version
変数を宣言し、文字列 Version:
に続いて、タブ文字\t
を出力しました。 ]、次に宣言された変数。
この時点で、変数Version
はdevelopment
として定義されており、これがこのアプリのデフォルトバージョンになります。 後で、この値をセマンティックバージョニング形式に従って配置された公式バージョン番号に変更します。
ファイルを保存して終了します。 これが完了したら、アプリケーションをビルドして実行し、正しいバージョンが出力されることを確認します。
go build ./app
次の出力が表示されます。
OutputVersion: development
これで、デフォルトのバージョン情報を出力するアプリケーションができましたが、ビルド時に現在のバージョン情報を渡す方法はまだありません。 次のステップでは、-ldflags
とgo build
を使用してこの問題を解決します。
ldflags
とgo build
の使用
前述のように、ldflags
はリンカーフラグの略で、Goツールチェーンの基になるリンカーにフラグを渡すために使用されます。 これは、次の構文に従って機能します。
go build -ldflags="-flag"
この例では、flag
をgo build
の一部として実行される基になるgo tool link
コマンドに渡しました。 このコマンドは、ldflags
に渡される内容を二重引用符で囲んで、文字やコマンドラインが必要な文字以外のものとして解釈する可能性のある文字を壊さないようにします。 ここから、多くの異なるリンクフラグを渡すことができます。 このチュートリアルでは、-X
フラグを使用して、リンク時に情報を変数に書き込み、その後に変数へのpackageパスとその新しい値を書き込みます。
go build -ldflags="-X 'package_path.variable_name=new_value'"
引用符の中には、-X
オプションと、変更する変数とその新しい値を表すキーと値のペアがあります。 .
文字は、パッケージパスと変数名を区切り、キーと値のペアで文字が壊れないように一重引用符が使用されます。
サンプルアプリケーションでVersion
変数を置き換えるには、最後のコマンドブロックの構文を使用して、新しい値を渡し、新しいバイナリを作成します。
go build -ldflags="-X 'main.Version=v1.0.0'"
このコマンドでは、main
はVersion
変数のパッケージパスです。これは、この変数がmain.go
ファイルにあるためです。 Version
は書き込み先の変数であり、v1.0.0
は新しい値です。
ldflags
を使用するには、変更する値が存在し、タイプstring
のパッケージレベル変数である必要があります。 この変数は、エクスポートまたは非エクスポートのいずれかです。 値をconst
にすることも、関数呼び出しの結果によって値を設定することもできません。 幸い、Version
は、次のすべての要件に適合します。main.go
ファイルで変数として既に宣言されており、現在の値(development
)と目的の値(v1.0.0
)は両方とも文字列です。
新しいapp
バイナリがビルドされたら、アプリケーションを実行します。
./app
次の出力が表示されます。
OutputVersion: v1.0.0
-ldflags
を使用して、Version
変数をdevelopment
からv1.0.0
に正常に変更しました。
これで、ビルド時に単純なアプリケーション内のstring
変数が変更されました。 ldflags
を使用すると、コマンドラインのみを使用して、バージョンの詳細やライセンス情報などを、配布可能なバイナリに埋め込むことができます。
この例では、変更した変数はmain
プログラムにあり、パス名を判別する際の問題を軽減しています。 ただし、これらの変数へのパスを見つけるのがより複雑な場合があります。 次のステップでは、サブパッケージの変数に値を書き込んで、より複雑なパッケージパスを決定するための最良の方法を示します。
サブパッケージ変数のターゲティング
前のセクションでは、アプリケーションの最上位パッケージにあるVersion
変数を操作しました。 しかし、これは常に当てはまるわけではありません。 main
はインポート可能なパッケージではないため、多くの場合、これらの変数を別のパッケージに配置する方が実用的です。 サンプルアプリケーションでこれをシミュレートするには、新しいサブパッケージapp/build
を作成します。このサブパッケージには、バイナリがビルドされた時刻と、ビルドコマンドを発行したユーザーの名前に関する情報が格納されます。
新しいサブパッケージを追加するには、最初にbuild
という名前の新しいディレクトリをプロジェクトに追加します。
mkdir -p build
次に、build.go
という名前の新しいファイルを作成して、新しい変数を保持します。
nano build/build.go
テキストエディタで、Time
およびUser
の新しい変数を追加します。
app / build / build.go
package build var Time string var User string
Time
変数は、バイナリが作成された時刻の文字列表現を保持します。 User
変数は、バイナリを作成したユーザーの名前を保持します。 これらの2つの変数には常に値があるため、Version
の場合のように、これらの変数をデフォルト値で初期化する必要はありません。
ファイルを保存して終了します。
次に、main.go
を開いて、これらの変数をアプリケーションに追加します。
nano main.go
main.go
の中に、次の強調表示された行を追加します。
main.go
package main import ( "app/build" "fmt" ) var Version = "development" func main() { fmt.Println("Version:\t", Version) fmt.Println("build.Time:\t", build.Time) fmt.Println("build.User:\t", build.User) }
これらの行では、最初にapp/build
パッケージをインポートし、次にVersion
を印刷したのと同じ方法でbuild.Time
とbuild.User
を印刷しました。
ファイルを保存して、テキストエディタを終了します。
次に、これらの変数をldflags
でターゲットにするには、インポートパスapp/build
に続いて、.User
または.Time
を使用できます。これは、インポートパスがすでにわかっているためです。 ただし、変数へのパスが明確でない、より複雑な状況をシミュレートするために、代わりにGoツールチェーンでnm
コマンドを使用してみましょう。
go tool nm
コマンドは、特定の実行可能ファイル、オブジェクトファイル、またはアーカイブに含まれるシンボルを出力します。 この場合、シンボルは、定義またはインポートされた変数や関数など、コード内のオブジェクトを参照します。 nm
でシンボルテーブルを生成し、grep
を使用して変数を検索することにより、そのパスに関する情報をすばやく見つけることができます。
注: nm
コマンドは、パッケージ名に ASCII 以外の文字、または"
文字が含まれている場合、変数のパスを見つけるのに役立ちません。 ]または%
文字。これは、ツール自体の制限です。
このコマンドを使用するには、最初にapp
のバイナリをビルドします。
go build
app
が作成されたので、nm
ツールをそのツールに向けて、出力を検索します。
go tool nm ./app | grep app
実行すると、nm
ツールは大量のデータを出力します。 このため、前のコマンドは|
を使用して出力をgrep
コマンドにパイプし、タイトルに最上位のapp
が含まれる用語を検索しました。
次のような出力が表示されます。
Output 55d2c0 D app/build.Time 55d2d0 D app/build.User 4069a0 T runtime.appendIntStr 462580 T strconv.appendEscapedRune . . .
この場合、結果セットの最初の2行には、探している2つの変数app/build.Time
とapp/build.User
へのパスが含まれています。
パスがわかったので、アプリケーションを再度ビルドします。今回は、ビルド時にVersion
、User
、およびTime
を変更します。 これを行うには、複数の-X
フラグを-ldflags
に渡します。
go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"
ここでは、id -u -n
Bashコマンドを渡して現在のユーザーを一覧表示し、date
コマンドを渡して現在の日付を一覧表示します。
実行可能ファイルがビルドされたら、プログラムを実行します。
./app
このコマンドをUnixシステムで実行すると、次のような出力が生成されます。
OutputVersion: v1.0.0 build.Time: Fri Oct 4 19:49:19 UTC 2019 build.User: sammy
これで、問題を解決するときに本番環境で重要な支援を提供できるバージョン管理とビルド情報を含むバイナリができました。
結論
このチュートリアルでは、ldflags
を正しく適用すると、ビルド時に貴重な情報をバイナリに挿入するための強力なツールになる方法を示しました。 このようにして、ソースコードに変更を加えることなく、機能フラグ、環境情報、バージョン情報などを制御できます。 現在のビルドワークフローにldflags
を追加することで、Goの自己完結型のバイナリ配布形式のメリットを最大化できます。
Goプログラミング言語の詳細については、Goシリーズのコーディング方法をご覧ください。 バージョン管理のその他のソリューションをお探しの場合は、Gitの使用方法リファレンスガイドをお試しください。