ldflagsを使用してGoアプリケーションのバージョン情報を設定する

前書き

実稼働環境にアプリケーションをデプロイする場合、バージョン情報やその他のメタデータを使用してバイナリを構築すると、識別情報を追加して長期にわたるビルドの追跡に役立つため、監視、ロギング、およびデバッグプロセスが向上します。 このバージョン情報には、ビルド時間、バイナリをビルドするマシンまたはユーザー、ビルド対象のVersion Control System (VCS)コミットIDなどの非常に動的なデータが含まれることがよくあります。 これらの値は絶えず変化するため、このデータをソースコードに直接コーディングし、新しいビルドが面倒でエラーが発生する前に変更します。ソースファイルが移動したり、variables/constantsが開発中にファイルを切り替えたりして、ビルドプロセスが中断する可能性があります。

Goでこれを解決する1つの方法は、go buildコマンドで-ldflagsを使用して、ソースコードを変更することなく、ビルド時に動的情報をバイナリに挿入することです。 このフラグでは、ldlinkerを表します。これは、コンパイルされたソースコードのさまざまな部分を最終的なバイナリにリンクするプログラムです。 したがって、ldflagslinker flagsを表します。 これは、基になるGoツールチェーンリンカーcmd/linkにフラグを渡すため、これと呼ばれます。これにより、ビルド時にコマンドラインからインポートされたパッケージの値を変更できます。

このチュートリアルでは、-ldflagsを使用してビルド時に変数の値を変更し、バージョン情報を画面に出力するサンプルアプリケーションを使用して、独自の動的情報をバイナリに導入します。

前提条件

この記事の例を実行するには、次のものが必要です。

サンプルアプリケーションの構築

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変数を宣言し、次にstringVersion:を出力し、次にタブ文字 を出力し、次に宣言しました変数。

この時点で、変数Versiondevelopmentとして定義されます。これは、このアプリのデフォルトバージョンになります。 後で、この値をsemantic versioning formatに従って配置された公式バージョン番号に変更します。

ファイルを保存して終了します。 これが完了したら、アプリケーションをビルドして実行し、正しいバージョンが印刷されることを確認します。

go build
./app

次の出力が表示されます。

OutputVersion:     development

これで、デフォルトのバージョン情報を出力するアプリケーションができましたが、ビルド時に現在のバージョン情報を渡す方法はまだありません。 次のステップでは、-ldflagsgo buildを使用してこの問題を解決します。

go buildldflagsを使用する

前述のように、ldflagslinker flagsを表し、Goツールチェーンの基になるリンカーにフラグを渡すために使用されます。 これは、次の構文に従って機能します。

go build -ldflags="-flag"

この例では、go buildの一部として実行される基になるgo tool linkコマンドにflagを渡しました。 このコマンドは、ldflagsに渡された内容を二重引用符で囲んで、その中の文字、またはコマンドラインが必要なもの以外のものとして解釈する可能性のある文字を壊さないようにします。 ここから、many different link flagsを渡すことができます。 このチュートリアルでは、-Xフラグを使用してリンク時に変数に情報を書き込み、続いて変数へのpackageパスとその新しい値を記述します。

go build -ldflags="-X 'package_path.variable_name=new_value'"

引用符の中には、-Xオプションと、変更する変数とその新しい値を表すkey-value pairがあります。 .文字は、パッケージパスと変数名を区切り、キーと値のペアで文字が壊れないように一重引用符が使用されます。

サンプルアプリケーションでVersion変数を置き換えるには、最後のコマンドブロックの構文を使用して新しい値を渡し、新しいバイナリを作成します。

go build -ldflags="-X 'main.Version=v1.0.0'"

このコマンドでは、mainVersion変数のパッケージパスです。これは、この変数が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

テキストエディタで、TimeUserの新しい変数を追加します。

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.Timebuild.Userを印刷しました。

ファイルを保存し、テキストエディターを終了します。

次に、これらの変数をldflagsでターゲットにするには、インポートパスがわかっているので、インポートパスapp/buildの後に.Userまたは.Timeを使用できます。 ただし、変数へのパスが明確でない、より複雑な状況をシミュレートするために、代わりにGoツールチェーンでnmコマンドを使用してみましょう。

go tool nmコマンドは、特定の実行可能ファイル、オブジェクトファイル、またはアーカイブに含まれるsymbolsを出力します。 この場合、シンボルは、定義またはインポートされた変数や関数など、コード内のオブジェクトを指します。 nmでシンボルテーブルを生成し、grepを使用して変数を検索することにより、そのパスに関する情報をすばやく見つけることができます。

[.note]#Note:パッケージ名にhttps://en.wikipedia.org/wiki/ASCII [ASCII以外のものがある場合、nmコマンドは変数のパスを見つけるのに役立ちません]文字、または"または%文字。これは、ツール自体の制限です。

このコマンドを使用するには、最初に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.Timeapp/build.Userへのパスが含まれています。

パスがわかったので、アプリケーションを再度ビルドします。今回は、ビルド時にVersionUser、および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プログラミング言語について詳しく知りたい場合は、完全なHow To Code in Go seriesを確認してください。 バージョン管理のソリューションをさらに探している場合は、How To Use Gitリファレンスガイドをお試しください。