【第二弾】Zen言語の標準ビルドスクリプトを使ってみた

#はじめに

第二弾の今回は!!
前回作成した『Zen言語でLチカをしてみた』プログラムを、
ビルドスクリプトを用いてコンパイルを自動化してみたいと思います。
[【前回】第一弾 : Zen言語でLチカをしてみた](https://qiita.com/freshers/items/44ad49188b53ead8431b)


[公式](https://www.zen-lang.org/ja-JP/docs/ch10-build-script)には、ビルドスクリプトについて、このように書かれています。
> Zenでビルドプロセスを自動化するには、ビルドスクリプトを使用します。従来では、このような目的でmakeやcmakeが利用されていました。Zenでは、外部ビルドシステムに依存せず、ビルドプロセスをZen言語で記述します。

なんと、Zen言語を用いてビルドスクリプトを記述できるってことらしいです！！
という事で、Zen言語の標準ビルドシステムを使い、コンパイルしてみたいと思います！！



#前提
- Zen言語バージョン : 0.8.20191124+552247019(Mac)
- モデル : Raspberry Pi 3 Model B

#Let's Try!!
## 全体の流れ
1. [ビルドスクリプト(build.zen)を生成と基本設定](#ステップ1ビルドスクリプトbuildzenを生成と基本設定)
2. [C言語ソースをビルドするステップを追加する](#ステップ2c言語ソースをビルドステップを追加する)  
(※ビルドステップについては、[ここ](https://www.zen-lang.org/ja-JP/docs/ch10-build-script/#ステップ)を見てください)

### 【ステップ1】ビルドスクリプト(build.zen)を生成と基本設定

#### ●build.zenの生成

直接、build.zenのファイルを作っても良いですが、
今回はZenプロジェクトを作成する事で、同時にbuild.zenも生成したいと思います。

``` sh
# 作業用ディレクトリへ移動
cd wiringPi-zen

# プロジェクトの作成
zen init-exe
```
上記を実行すると、以下のようなbuild.zenが生成されると思います。
これをベースとし今回のプロジェクトで必要なソースをビルドしていきたいと思います。

``` java:build.zen
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const mode = b.standardReleaseOptions();
    const exe = b.addExecutable("wiringPi-zen", "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);
}

```

#### ●生成されたbuild.zenについて

 では、生成されたbuild.zenについて今回必要な情報について見ていきましょう。

`const exe = b.addExecutable("wiringPi-zen", "src/main.zen");`
これは、先ほどからちらほら出てくる「ビルドステップ」を作成する命令の一つです。

このaddExecutable()は、Builderのメソッドであり、
**「実行バイナリをビルドするステップ」**を作成する命令となります。

このステップを起点としてオプションを指定することが出来ます。
今回も、このステップに様々オプション(後ほど説明)を追加しています。
最終的にビルドが実行されるタイミングは、`exe.install();`の時になります。


　Zen言語では、ビルドモードが存在し「実行速度優先」や「安全性優先」など、
場面に合わせてビルド方法を変更することができます。

ビルド時に指定がない場合は、デバッグモードで実行されます。
しかし、[リリースモード](https://www.zen-lang.org/ja-JP/docs/ch10-build-options/#%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89)でビルドしたい場合は、以下のステップは必要です。

`const mode = b.standardReleaseOptions();`
`exe.setBuildMode(mode);`

これがある事で、ビルド時にリリースモードの指定(-Drelease-safe / -Drelease-fast / -Drelease-safe)を行えるようになります。

(※[ここ](https://www.zen-lang.org/ja-JP/docs/ch10-build-script/#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E3%83%93%E3%83%AB%E3%83%89%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88)も同時にご覧ください。)

#### ●今回のフォルダ構成

因みに、wiringPiライブラリのソースも必要なので、
以下のようなディレクトリ構造として必要なファイルをまとめました。

``` tree
.
├── WiringPi
│   └── wiringPi
│       ├── piHiPri.c
│       ├── softPwm.c
│       ├── softPwm.h
│       ├── softTone.c
│       ├── softTone.h
│       ├── wiringPi.c
│       └── wiringPi.h
├── build.zen(※生成されたファイル)
└── src
    └── main.zen(※生成されたファイル)
  
```

#### ●ビルドステップへの基本設定

##### [クロスビルド]
今回は、Raspberry Pi 3で動作させたいですが、
開発環境はMacを用いているためクロスビルドを行う必要があります。

Zen言語はコンパイル時にターゲットを指定する事で、
簡単にクロスビルドを行うことが出来ました。

コマンドビルドを行う際に指定した`-target armv7-linux-gnueabihf`は、
ビルドスクリプトでは、下記のように記述出来ます。


```java

const target = try Target.parse("armv7-linux-gnueabihf");

// ビルドステップにターゲットを設定する
exe.setTheTarget(target);
```

##### [インクルドパスの指定]

`-I WiringPi/wiringPi`は、下記のように記述できます。

```java
const C_HEADER_PATH = "WiringPi/wiringPi";

// ビルドステップにパスを設定する
exe.addIncludeDir(C_HEADER_PATH);
```

##### [システムライブラリ(C言語の標準関数など)パスの指定]

`--library c`は、下記のように記述出来ます。

```java

// ビルドステップにC言語の標準関数を
exe.linkSystemLibrary("c");
```

現在のビルドスクリプトは下記になります。
※今回は、実行ステップは使用しないため、コメントアウトしています。

```java:build.zen
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) !void {

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

    // ビルドモードを設定 //
    // コマンドラインから-Drelease-safe / -Drelease-fast / -Drelease-safeのいずれかを選択できるようになる //
    const mode = b.standardReleaseOptions();
    exe.setBuildMode(mode);

    // ビルドステップにターゲットを設定する //
    const target = try Target.parse("armv7-linux-gnueabihf");
    exe.setTheTarget(target);

    // ビルドステップにパスを設定する //
    const C_HEADER_PATH = "WiringPi/wiringPi/";
    exe.addIncludeDir(C_HEADER_PATH);

    // ビルドステップにC言語の標準関数を  //
    exe.linkSystemLibrary("c");

    // 実行バイナリの生成 //
    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);
}
```

### 【ステップ2】C言語ソースをビルドステップを追加する

コマンドビルドと同様に、
build.zenでC言語のソースがビルドできます。

そこで、C言語ソースをビルドするために、
C言語ソースをビルドステップに追加する、以下の関数を用います。

`addCSourceFile(file: []const u8, args: []const []const u8) void`

``` java
const c_source = [_][]const u8{
    "wiringPi",
    "piHiPri",
    "softTone",
    "softPwm"
};

// Wiring Pi ライブラリのコンパイル //
inline for(c_source) |source| {

    // C言語ソースをビルド対象として追加する
    exe.addCSourceFile(C_HEADER_PATH ++ source ++ ".c", [_][]const u8{});

}
```


## 完成

参考までに全体のビルドスクリプトを貼り付けておきます。
では、コンソールに`zen build`と入力してビルドしましょう!!

``` java:build.zen
const std = @import("std");
const Target = std.build.Target;
const Builder = std.build.Builder;

pub fn build(b: *Builder) !void {

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

    // リリースモードの設定(指定がない場合はデバッグモード) //
    // コマンドラインから-Drelease-safe / -Drelease-fast / -Drelease-safeのいずれかを選択できるようになる //
    const mode = b.standardReleaseOptions();
    exe.setBuildMode(mode);

    // ビルドステップにターゲットを設定する //
    const target = try Target.parse("armv7-linux-gnueabihf");
    exe.setTheTarget(target);

    // ビルドステップにパスを設定する //
    const C_HEADER_PATH = "WiringPi/wiringPi/";
    exe.addIncludeDir(C_HEADER_PATH);

    // ビルドステップにC言語の標準関数を  //
    exe.linkSystemLibrary("c");

    // 出力ディレクトリの設定 //
    exe.setOutputDir("./");

    const c_source = [_][]const u8{
        "wiringPi",
        "piHiPri",
        "softTone",
        "softPwm"
    };

    // Wiring Pi ライブラリのコンパイル //
    inline for(c_source) |source| {

        // C言語ソースをビルド対象として追加する
        exe.addCSourceFile(C_HEADER_PATH ++ source ++ ".c", [_][]const u8{});

    }

    // 実行バイナリの生成 //
    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言語の標準ビルドスクリプトを使ってみた」は以上になります。
もし勘違いしている点があれば、ご指摘頂ければ幸いです。

次回は「Zen言語で作ったRaspberry Pi用のLチカライブラリ」を作っていきます！

以上、最後まで読んで頂きありがとうございました。