序章

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

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

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

前提条件

この記事の例に従うには、次のものが必要です。

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

ldflagsを使用して動的データを導入する前に、まず情報を挿入するアプリケーションが必要です。 このステップでは、このアプリケーションを作成します。このアプリケーションは、この段階では静的なバージョン情報のみを出力します。 それでは、そのアプリケーションを作成しましょう。

srcディレクトリに、アプリケーションにちなんで名付けられたディレクトリを作成します。 このチュートリアルでは、アプリケーション名appを使用します。

  1. mkdir app

作業ディレクトリを次のフォルダに変更します。

  1. cd app

次に、選択したテキストエディタを使用して、プログラムのエントリポイントmain.goを作成します。

  1. nano main.go

次に、次のコンテンツを追加して、アプリケーションにバージョン情報を印刷させます。

app / main.go
package main

import (
	"fmt"
)

var Version = "development"

func main() {
	fmt.Println("Version:\t", Version)
}

main()関数内で、Version変数を宣言し、文字列 Version:に続いて、タブ文字\tを出力しました。 ]、次に宣言された変数。

この時点で、変数Versiondevelopmentとして定義されており、これがこのアプリのデフォルトバージョンになります。 後で、この値をセマンティックバージョン管理形式に従って配置された公式バージョン番号に変更します。

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

  1. go build
  2. ./app

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

  1. Output
    Version: development

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

ldflagsgo buildの使用

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

  1. go build -ldflags="-flag"

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

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

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

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

  1. 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バイナリがビルドされたら、アプリケーションを実行します。

  1. ./app

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

  1. Output
    Version: v1.0.0

-ldflagsを使用して、Version変数をdevelopmentからv1.0.0に変更しました。

これで、ビルド時に単純なアプリケーション内のstring変数が変更されました。 ldflagsを使用すると、コマンドラインのみを使用して、バージョンの詳細やライセンス情報などを、配布可能なバイナリに埋め込むことができます。

この例では、変更した変数はmainプログラムにあり、パス名を判別する際の問題を軽減しています。 ただし、これらの変数へのパスを見つけるのがより複雑な場合があります。 次のステップでは、サブパッケージ内の変数に値を書き込んで、より複雑なパッケージパスを決定するための最良の方法を示します。

サブパッケージ変数のターゲティング

前のセクションでは、アプリケーションの最上位パッケージにあるVersion変数を操作しました。 しかし、これは常に当てはまるわけではありません。 mainはインポート可能なパッケージではないため、多くの場合、これらの変数を別のパッケージに配置する方が実用的です。 サンプルアプリケーションでこれをシミュレートするには、新しいサブパッケージapp/buildを作成します。このサブパッケージには、バイナリがビルドされた時刻と、ビルドコマンドを発行したユーザーの名前に関する情報が格納されます。

新しいサブパッケージを追加するには、最初にbuildという名前の新しいディレクトリをプロジェクトに追加します。

  1. mkdir -p build

次に、build.goという名前の新しいファイルを作成して、新しい変数を保持します。

  1. nano build/build.go

テキストエディタで、TimeおよびUserの新しい変数を追加します。

app / build / build.go
package build

var Time string

var User string

Time変数は、バイナリが作成された時刻の文字列表現を保持します。 User変数は、バイナリを作成したユーザーの名前を保持します。 これらの2つの変数には常に値があるため、Versionの場合のように、これらの変数をデフォルト値で初期化する必要はありません。

ファイルを保存して終了します。

次に、main.goを開いて、これらの変数をアプリケーションに追加します。

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

注: nmコマンドは、パッケージ名に ASCII 以外の文字、または"が含まれている場合、変数のパスを見つけるのに役立ちません。 ]または%文字。これは、ツール自体の制限です。

このコマンドを使用するには、最初にappのバイナリをビルドします。

  1. go build

appが作成されたので、nmツールをそのツールに向けて、出力を検索します。

  1. 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に渡します。

  1. 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コマンドを渡して現在の日付を一覧表示します。

実行可能ファイルがビルドされたら、プログラムを実行します。

  1. ./app

このコマンドをUnixシステムで実行すると、次のような出力が生成されます。

Output
Version: v1.0.0 build.Time: Fri Oct 4 19:49:19 UTC 2019 build.User: sammy

これで、問題を解決するときに本番環境で重要な支援を提供できるバージョン管理とビルド情報を含むバイナリができました。

結論

このチュートリアルでは、ldflagsを正しく適用すると、ビルド時に貴重な情報をバイナリに挿入するための強力なツールになる方法を示しました。 このようにして、ソースコードに変更を加えることなく、機能フラグ、環境情報、バージョン情報などを制御できます。 現在のビルドワークフローにldflagsを追加することで、Goの自己完結型のバイナリ配布形式のメリットを最大化できます。

Goプログラミング言語の詳細については、Goシリーズのコーディング方法をご覧ください。 バージョン管理のその他のソリューションをお探しの場合は、Gitの使用方法リファレンスガイドをお試しください。