オフィスアワーがそろそろ始まるよ!()

ビルドスクリプト

Zenでビルドプロセスを自動化するには、ビルドスクリプトを使用します。従来では、このような目的でmakecmakeが利用されていました。Zenでは、外部ビルドシステムに依存せず、ビルドプロセスをZen言語で記述します。

Zenのビルドスクリプトは、次のようなユースケースで利用できます。

  • ビルド前処理
    • ホストシステム内にある依存ライブラリの検出
    • 依存ライブラリのビルド
    • プラットフォーム固有の設定
  • ビルド
    • オプションの指定 (リリースモード、ターゲットなど)
  • ビルド後処理
    • バイナリ形式の変換 (ELFからIntel HEX形式への変換)
    • バイナリの検査と編集 (binutilsの使用など)
  • テスト
    • テスト実行
    • コードフォーマッタ実行

このような異なるユースケースに対して、Zenのビルドシステムで対応可能です。

用語解説

具体的な説明に入る前に、ステップオプションという2つの概念について説明します。

ステップ

ビルドプロセスの1つのステージをステップと呼びます。ステップはビルドスクリプト内で自由に追加することができます。また、ステップ同士に依存関係を持たせることができます。

例えば、build-exerundebugという3つのステップがあるとします。build-exeステップが完了しないと、runステップおよびdebugステップは実行できません。そこで、「build-exe -> run」という依存関係と、「build-exe -> debug」という依存関係をビルドスクリプト内で記述します。

下記に示す3つのコマンドは、指定されたステップと依存するステップだけを実行します。

# `build-exe`ステップのみ実行
$ zen build build-exe
# `build-exe`ステップと`run`ステップを実行
$ zen build run
# `build-exe`ステップと`debug`ステップを実行
$ zen build debug

このように、ステップごとの依存関係を記述しながら、柔軟なビルドプロセスを組み上げることができるようになっています。

オプション

ビルドスクリプトを実行する際に、コマンドラインから与えられるオプションです。現在、ブール値 (true / flase) と文字列とをオプションとして受け取り、ビルドスクリプト内で使用することができます。

コマンドラインからオプションを与えるには、-Dの後にオプションを続けます。

ブール値の場合、-D<オプション>で該当オプションがtrueになります。文字列の場合、-D<オプション>=<文字列>の形式でオプションを与えます。

プロジェクトテンプレートのビルドスクリプト

まず、感覚を掴むために、プロジェクトテンプレートのビルドスクリプトを見てみましょう。

新しくプロジェクトテンプレートを作成します。

$ mkdir template && cd $_
$ zen init-exe

build.zenは以下の内容になっています。

examples/ch10-compilation/build-script/template/build.zen:1:14

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const mode = b.standardReleaseOptions();
    const exe = b.addExecutable("template", "src/main.zen");
    exe.setBuildMode(mode);
    exe.install();

    const run_cmd = exe.run();
    run_cmd.step.dependOn(b.getInstallStep());

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

ビルドスクリプトの内容を説明する前に、このビルドスクリプトによってできることを見てみましょう。次のコマンドで、ビルドスクリプトのヘルプを表示することができます。

$ zen build --help

次のようなヘルプが表示されます。

Usage: zen build [steps] [options]

Steps:
  install (default)      Copy build artifacts to prefix path
  uninstall              Remove build artifacts from prefix path
  run                    Run the app

General Options:
  --help                 Print this help and exit
  --verbose              Print commands before executing them
...

Project-Specific Options:
  -Drelease-safe=[bool]  optimizations on and safety on
  -Drelease-fast=[bool]  optimizations on and safety off
  -Drelease-small=[bool] size optimizations on and safety off

Advanced Options:\n"
  --build-file [file]      Override path to build.zen
  --sign [identity]        Sign binary files with a signing identity
...

ビルドステップにはinstall / uninstall / runという3つのステップがあり、installがデフォルトになっています。zen buildの後に何もステップを指定しなければ、installが実行されます。プロジェクトテンプレートでは、installはビルドキャッシュ (zen-cache) 内にbinというディレクトリを作って、その下にアプリケーションバイナリをコピーします。

runステップはビルドしたアプリケーションバイナリを実行します。

$ zen build
# ビルド生成物を確認
$ ./zen-cache/bin/template
Congratulations on your first step to writing perfect software in Zen.

# 下は、上記2つのコマンドを実行したことと同義
$ zen build run
Congratulations on your first step to writing perfect software in Zen.

# リリースモードをオプションとして与える
$ zen build run -Dreleaase-safe
Congratulations on your first step to writing perfect software in Zen.

さて、使い方がわかったところで、ビルドスクリプトの中身に戻りましょう。

ビルドスクリプトではstd.buildモジュール内のBuilder構造体を使用するので、インポートします。

const Builder = @import("std").build.Builder;

ビルドスクリプトのエントリポイントは、pub fn build(*Builder) void、もしくは、エラーを返す場合pub fn build(*Builder) !voidでなければなりません。プロジェクトテンプレートでは、bという変数名で*Builderを受け取っています。

pub fn build(b: *Builder) void {

コマンドラインオプションからビルドモードを指定したい場合、standardReleaseOptions関数を使用できます。コマンドラインから-Drelease-safe / -Drelease-fast / -Drelease-safeのいずれかを選択できるようになります。コマンドラインで指定がない場合は、ビルドモードはデバッグモードになります。

    const mode = b.standardReleaseOptions();

addExecutable関数では、アプリケーション名とルートソースファイルを指定して、アプリケーションバイナリをビルドするためのexeステップを得ます。Builderはいくつかの頻出ステップを作成する関数を有しています。addExecutableはそのうちの1つです。

    const exe = b.addExecutable("template", "src/main.zen");

exe.setBuildModeexeステップのビルドモードを設定し、exe.installinstallステップでexeステップが実行されるようにします。

    exe.setBuildMode(mode);
    exe.install();

ここまでで、zen buildもしくはzen build installでアプリケーションバイナリがビルドされ、zen-cache/binにビルドされたバイナリがコピーされます。

exe.run関数は、exeステップでの生成物を実行するrun_cmdステップを返します。バイナリを実行するステップは、installステップに依存するため、run_cmd.step.dependOninstallステップとの依存関係を作成しています。

    const run_cmd = exe.run();
    run_cmd.step.dependOn(b.getInstallStep());

最後にコマンドラインから選択できるステップにrunステップ (コード内ではrun_step) を追加します。これには、Builderstep関数を使用します。引数は、ステップ名とそのステップの説明です。このrun_stepは、コマンドラインでrunが入力された時に実行します。run_cmdステップとの依存関係を作成することで、runステップ実行によって、run_cmdステップが実行されることになります。

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

これでプロジェクトテンプレートのビルドスクリプトを一通り解説しました。ビルドスクリプトは最初はとっつきにくく感じるかもしれません。しかし基本は、ステップを作って、ステップ間の依存関係を接続していくだけです。

std.build内にあるステップ

ビルドスクリプトでは自由にステップを追加することが可能ですが、よく使うステップは、std.buildの中で定義されています。

  • LibExeObjStep
  • RunStep
  • InstallArtifactStep
  • InstallFileStep
  • InstallDirStep
  • WriteFileStep
  • LogStep
  • RemoveDirStep
  • CodeSignStep

これらのステップで目的のビルドプロセスが実現できない場合、ビルドステップを追加すると良いでしょう。

APIリファレンス

Builder

Builderのインスタンスは、build関数の引数として、渡されます。以下はインスタンスから利用できるメソッドです。

  • addExecutable(name: []const u8, root_src: ?[]const u8) *LibExeObjStep: 実行バイナリをビルドするステップを作成します。nameは実行ファイル名、root_srcは最も上位の関数があるソースファイルです。Cソースファイルをビルドする場合は、root_srcnullでもかまいません。
  • addObject(name: []const u8, root_src: ?[]const u8) *LibExeObjStep: オブジェクトファイルをビルドするステップを作成します。
  • addSharedLibrary(name: []const u8, root_src: ?[]const u8, ver: Version) *LibExeObjStep: 共有ライブラリをビルドするステップを作成します。
  • addStaticLibrary(name: []const u8, root_src: ?[]const u8) *LibExeObjStep: 静的ライブラリをビルドするステップを作成します。
  • addTest(root_src: []const u8) *LibExeObjStep: テストバイナリを生成するステップを追加します。
  • addAssemble(name: []const u8, src: []const u8) *LibExeObjStep: アセンブリからオブジェクトファイルをビルドするステップを作成します。
  • addSystemCommand(argv: []const []const u8) *RunStep: システムコマンドを実行するステップを作成します。
  • addWriteFile(file_path: []const u8, data: []const u8) *WriteFileStep: ファイルにデータを書き込むステップを作成します。
  • addLog(comptime format: []const u8, args: ...) *LogStep: ログ出力するステップを作成します。
  • addRemoveDirTree(dir_path: []const u8) *RemoveDirStep: ディレクトリを削除するステップを作成します。
  • addFmt(self: *Builder, paths: []const []const u8) *FmtStep: コードフォーマッタを実行するステップを作成します。
  • addNativeSystemIncludeDir(path: []const u8) void: ネイティブシステムのインクルードパスを追加します。
  • ddNativeSystemLibPath(path: []const u8) void: ネイティブシステムのライブラリパスを追加します。
  • option(comptime T: type, name: []const u8, description: []const u8) ?T: ビルドスクリプトでビルドする際に、コマンドラインから与えられるオプションを追加します。
  • step(name: []const u8, description: []const u8) *Step: 新しくステップを作成します。
  • setPreferredReleaseMode(mode: builtin.Mode) void: ユーザーがビルドモードを指定しなかった場合に使われるデフォルトのビルドモードです。
  • standardReleaseOptions(self: *Builder) builtin.Mode: release-saferelease-fastrelease-smallをビルドオプションとして追加し、コマンドラインで指定されたビルドモードを返します。
  • installArtifact(artifact: *LibExeObjStep) void: 成果物をインストールします。
  • addInstallArtifact(artifact: *LibExeObjStep) *InstallArtifactStep: 成果物をインストールするステップを追加します。
  • exec(argv: []const []const u8) ![]u8: コマンドを実行します。

LibExeObjStep

実行バイナリやライブラリ、オブジェクトファイルをビルドするステップです。

  • setTheTarget(target: Target) void: ビルドターゲットを設定します。
  • setOutputDir(dir: []const u8) void: ビルド成果物を配置するディレクトリを設定します。
  • install() void: 成果物をインストールします。
  • run() *RunStep: 実行ステップを追加します。コマンドライン引数にrunステップを追加します。
  • setLinkerScriptPath(path: []const u8) void: リンカスクリプトのパスを設定します。
  • linkLibrary(lib: *LibExeObjStep) void: 他ステップでビルドされるライブラリをリンクします。
  • linkSystemLibrary(name: []const u8) void: システムライブラリをリンクします。
  • addCSourceFile(file: []const u8, args: []const []const u8) void: Cソースファイルをビルド対象として追加します。argsにはCソースコードのビルドオプション (clangのオプション) を複数設定できます。
  • setBuildMode(mode: builtin.Mode) void: ビルドモードを設定します。
  • getOutputPath() []const u8: 成果物が出力されるパスを取得します。成果物にさらに加工を加えたい場合に使います。
  • addAssemblyFile(self: *LibExeObjStep, path: []const u8) void: アセンブリファイルをビルド対象として追加します。
  • addObjectFile(path: []const u8) void: オブジェクトファイルをビルド対象として追加します。
  • addObject(obj: *LibExeObjStep) void: 他ステップでビルドされるオブジェクトを追加します。
  • addIncludeDir(path: []const u8) void: インクルードパスを追加します。
  • addLibPath(path: []const u8) void: ライブラリのサーチパスを追加します。

クロスビルド

std.build.Targetparse関数にターゲットトリプルの文字列を渡すと、Target構造体のインスタンスが作成されます。これを、ビルドステップのsetTheTargetに渡します。

    const target = try Target.parse("armv7m-freestanding-eabi");

    // `exe`は実行ファイルビルドステップ
    exe.setTheTarget(target);

サポートしているターゲットは、以下のコマンドで確認します。

zen targets

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.