ビルドタグを使用したGoバイナリのカスタマイズ

前書き

Goでは、build tag、つまりビルド制約は、buildプロセス中にファイルをパッケージに含めるタイミングを決定するコードに追加される識別子です。 これにより、同じソースコードからGoアプリケーションのさまざまなバージョンを構築し、それらを迅速かつ体系的に切り替えることができます。 多くの開発者はビルドタグを使用して、異なるオペレーティングシステム間の差異を考慮してコードの変更を必要とするプログラムなど、クロスプラットフォーム互換のアプリケーションを構築するワークフローを改善します。 ビルドタグはintegration testingにも使用されるため、統合コードとmock service or stubを含むコードをすばやく切り替えたり、アプリケーション内のさまざまなレベルの機能セットに使用したりできます。

顧客の機能セットが異なるという問題を例として見てみましょう。 一部のアプリケーションを作成するときは、FreePro、およびEnterpriseレベルを提供するアプリケーションなど、バイナリに含める機能を制御したい場合があります。 顧客がこれらのアプリケーションのサブスクリプションレベルを上げると、より多くの機能がロック解除されて利用可能になります。 この問題を解決するには、別々のプロジェクトを維持し、importステートメントを使用してそれらの同期を維持しようとします。 このアプローチは機能しますが、時間が経つと退屈でエラーが発生しやすくなります。 別のアプローチは、ビルドタグを使用することです。

この記事では、Goでビルドタグを使用して、サンプルアプリケーションのFree、Pro、およびEnterpriseの機能セットを提供するさまざまな実行可能バイナリを生成します。 それぞれに異なる機能セットが用意されており、無料版がデフォルトです。

前提条件

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

無料版の構築

ビルドタグなしでgo buildを実行するとデフォルトになるため、アプリケーションの無料バージョンをビルドすることから始めましょう。 後で、ビルドタグを使用して、プログラムに他のパーツを選択的に追加します。

srcディレクトリに、アプリケーションの名前でフォルダを作成します。 このチュートリアルでは、appを使用します。

mkdir app

このフォルダーに移動します。

cd app

次に、選択したテキストエディタでmain.goという名前の新しいテキストファイルを作成します。

nano main.go

次に、アプリケーションの無料版を定義します。 次の内容をmain.goに追加します。

main.go

package main

import "fmt"

var features = []string{
  "Free Feature #1",
  "Free Feature #2",
}

func main() {
  for _, f := range features {
    fmt.Println(">", f)
  }
}

このファイルでは、featuresという名前のsliceを宣言するプログラムを作成しました。このプログラムは、Freeアプリケーションの機能を表す2つのstringsを保持しています。 アプリケーションのmain()関数は、featuresスライスを介してfor loop to rangeを使用し、画面で使用可能なすべての機能を出力します。

ファイルを保存して終了します。 このファイルが保存されたので、記事の残りの部分で編集する必要はなくなりました。 代わりに、ビルドタグを使用して、そこからビルドするバイナリの機能を変更します。

プログラムをビルドして実行します。

go build
./app

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

Output> Free Feature #1
> Free Feature #2

プログラムは2つの無料機能を印刷し、アプリの無料版を完成させました。

これまでのところ、非常に基本的な機能セットを持つアプリケーションを作成しました。 次に、ビルド時にアプリケーションに機能を追加する方法を構築します。

go buildを使用したPro機能の追加

これまで、main.goに変更を加えることを避け、メインコードを変更したり、場合によっては壊したりせずにコードを追加する必要がある一般的な本番環境をシミュレートしました。 main.goファイルを編集できないため、ビルドタグを使用してfeaturesスライスにさらに多くの機能を挿入するための別のメカニズムを使用する必要があります。

init()関数を使用してfeaturesスライスに機能を追加するpro.goという新しいファイルを作成しましょう。

nano pro.go

エディターがファイルを開いたら、次の行を追加します。

pro.go

package main

func init() {
  features = append(features,
    "Pro Feature #1",
    "Pro Feature #2",
  )
}

このコードでは、init()を使用して、アプリケーションのmain()関数の前にコードを実行し、続いてappend()を使用してPro機能をfeaturesスライスに追加しました。 ファイルを保存して終了します。

go buildを使用してアプリケーションをコンパイルして実行します。

go build

現在のディレクトリには2つのファイル(pro.gomain.go)があるため、go buildは両方からバイナリを作成します。 このバイナリを実行します:

./app

これにより、次の機能セットが提供されます。

Output> Free Feature #1
> Free Feature #2
> Pro Feature #1
> Pro Feature #2

このアプリケーションには、Pro機能とFree機能の両方が含まれています。 ただし、これは望ましくありません。バージョンに区別がないため、無料版には現在、Proバージョンでのみ使用できるはずの機能が含まれています。 これを修正するには、アプリケーションのさまざまな層を管理するコードを追加するか、ビルドタグを使用して、ビルドする.goファイルと無視するファイルをGoツールチェーンに指示します。 次のステップでビルドタグを追加しましょう。

ビルドタグの追加

ビルドタグを使用して、アプリケーションのProバージョンと無料バージョンを区別できるようになりました。

ビルドタグがどのように見えるかを調べることから始めましょう。

// +build tag_name

このコード行をパッケージの最初の行として配置し、tag_nameをビルドタグの名前に置き換えることで、このパッケージに、最終的なバイナリに選択的に含めることができるコードとしてタグを付けることができます。 ビルドタグをpro.goファイルに追加して、タグが指定されていない限り無視するようにgo buildコマンドに指示して、これが実際に動作することを確認しましょう。 テキストエディターでファイルを開きます。

nano pro.go

次に、強調表示された次の行を追加します。

pro.go

// +build pro

package main

func init() {
  features = append(features,
    "Pro Feature #1",
    "Pro Feature #2",
  )
}

pro.goファイルの先頭に、// +build proを追加し、その後に空白の改行を追加しました。 この末尾の改行は必須です。それ以外の場合、Goはこれをコメントとして解釈します。 ビルドタグ宣言も.goファイルの一番上にある必要があります。 ビルドタグの上にあるものは、コメントでさえありません。

+build宣言は、これがコメントではなく、ビルドタグであることをgo buildコマンドに通知します。 2番目の部分はproタグです。 このタグをpro.goファイルの先頭に追加することにより、go buildコマンドには、proタグが存在するpro.goファイルのみが含まれるようになります。

アプリケーションをコンパイルして再度実行します。

go build
./app

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

Output> Free Feature #1
> Free Feature #2

pro.goファイルにはproタグが存在する必要があるため、ファイルは無視され、アプリケーションはそれなしでコンパイルされます。

go buildコマンドを実行する場合、-tagsフラグを使用して、タグ自体を引数として追加することにより、コンパイルされたソースに条件付きでコードを含めることができます。 proタグに対してこれを実行しましょう。

go build -tags pro

これは以下を出力します。

Output> Free Feature #1
> Free Feature #2
> Pro Feature #1
> Pro Feature #2

これで、proビルドタグを使用してアプリケーションをビルドするときにのみ、追加機能を取得できます。

バージョンが2つしかない場合はこれで問題ありませんが、タグを追加すると複雑になります。 次のステップでアプリのエンタープライズバージョンを追加するには、ブールロジックで結合された複数のビルドタグを使用します。

ビルドタグブールロジック

Goパッケージに複数のビルドタグがある場合、タグはBoolean logicを使用して相互作用します。 これを示すために、proタグとenterpriseタグの両方を使用してアプリケーションのエンタープライズレベルを追加します。

エンタープライズバイナリを構築するには、デフォルト機能、プロレベルの機能、およびエンタープライズ向けの新しい機能セットの両方を含める必要があります。 まず、エディターを開き、新しいエンタープライズ機能を追加する新しいファイルenterprise.goを作成します。

nano enterprise.go

enterprise.goの内容は、pro.goとほぼ同じように見えますが、新しい機能が含まれています。 ファイルに次の行を追加します。

enterprise.go

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

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

現在、enterprise.goファイルにはビルドタグがありません。pro.goを追加したときに学習したように、これは、go.buildの実行時にこれらの機能が無料バージョンに追加されることを意味します。 pro.goの場合、ファイルの先頭に// +build proと改行を追加して、-tags proが使用されている場合にのみ含める必要があることをgo buildに通知しました。 この状況では、目標を達成するために必要なビルドタグは1つだけです。 ただし、新しいエンタープライズ機能を追加する場合は、最初にPro機能も必要です。

まず、proビルドタグのサポートをenterprise.goに追加しましょう。 テキストエディターでファイルを開きます。

nano enterprise.go

次に、package main宣言の前にビルドタグを追加し、ビルドタグの後に改行を含めるようにしてください。

enterprise.go

// +build pro

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

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

タグなしでアプリケーションをコンパイルして実行します。

go build
./app

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

Output> Free Feature #1
> Free Feature #2

エンタープライズ機能は、無料版では表示されなくなりました。 次に、proビルドタグを追加して、アプリケーションをビルドして再度実行します。

go build -tags pro
./app

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

Output> Free Feature #1
> Free Feature #2
> Enterprise Feature #1
> Enterprise Feature #2
> Pro Feature #1
> Pro Feature #2

これはまだ必要なものではありません。Proバージョンをビルドしようとすると、エンタープライズ機能が表示されるようになりました。 これを解決するには、別のビルドタグを使用する必要があります。 ただし、proタグとは異なり、pro機能とenterprise機能の両方が使用可能であることを確認する必要があります。

Goビルドシステムは、ビルドタグシステムでいくつかの基本的なブールロジックの使用を許可することにより、この状況を考慮します。

enterprise.goをもう一度開きましょう:

nano enterprise.go

proタグと同じ行に別のビルドタグenterpriseを追加します。

enterprise.go

// +build pro enterprise

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

ファイルを保存して閉じます。

それでは、新しいenterpriseビルドタグを使用してアプリケーションをコンパイルして実行しましょう。

go build -tags enterprise
./app

これにより、以下が得られます。

Output> Free Feature #1
> Free Feature #2
> Enterprise Feature #1
> Enterprise Feature #2

Pro機能が失われました。 これは、.goファイルの同じ行に複数のビルドタグを配置すると、go buildがそれらをORロジックを使用していると解釈するためです。 行// +build pro enterpriseを追加すると、eitherproビルドタグであるか、enterpriseビルドタグが存在する場合、enterprise.goファイルがビルドされます。 ビルドタグを正しく設定してbothを要求し、代わりにANDロジックを使用する必要があります。

両方のタグを同じ行に配置する代わりに、別々の行に配置すると、go buildANDロジックを使用してそれらのタグを解釈します。

もう一度enterprise.goを開き、ビルドタグを複数の行に分けましょう。

enterprise.go

// +build pro
// +build enterprise

package main

func init() {
  features = append(features,
    "Enterprise Feature #1",
    "Enterprise Feature #2",
  )
}

次に、新しいenterpriseビルドタグを使用してアプリケーションをコンパイルして実行します。

go build -tags enterprise
./app

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

Output> Free Feature #1
> Free Feature #2

まだ完全ではありません。ANDステートメントでは両方の要素をtrueと見なす必要があるため、proenterpriseの両方のビルドタグを使用する必要があります。

もう一度試してみましょう。

go build -tags "enterprise pro"
./app

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

Output> Free Feature #1
> Free Feature #2
> Enterprise Feature #1
> Enterprise Feature #2
> Pro Feature #1
> Pro Feature #2

これで、複数の方法で同じソースツリーからアプリケーションを構築し、それに応じてアプリケーションの機能をロック解除できます。

この例では、新しい// +buildタグを使用してANDロジックを示していますが、ビルドタグを使用してブールロジックを表す別の方法があります。 次の表には、ビルドタグの他の構文フォーマットの例と、それに相当するブール値が含まれています。

ビルドタグの構文 タグサンプルの作成 ブールステートメント

スペースで区切られた要素

// +build pro enterprise

proまたはenterprise

カンマ区切りの要素

// +build pro,enterprise

proおよびenterprise

感嘆符の要素

// +build !pro

proではありません

結論

このチュートリアルでは、ビルドタグを使用して、どのコードをバイナリにコンパイルするかを制御できるようにしました。 最初に、ビルドタグを宣言し、それらをgo buildで使用し、次にブール論理で複数のタグを組み合わせました。 次に、無料版、プロ版、およびエンタープライズ版のさまざまな機能セットを表すプログラムを作成し、ビルドタグでプロジェクトを制御できる強力なレベルの制御を示しました。

ビルドタグについて詳しく知りたい場合は、Golang documentation on the subjectを確認するか、引き続きHow To Code in Go seriesを調べてください。