課題14: ビルドシステムの実践

マンダトリー要件 (80点)

Part 1: 基本的なbuild.zigの作成 (20点)

実行ファイル、ライブラリ、テストを含むbuild.zigを作成してください。

ディレクトリ構造:

part1/
├── build.zig
├── src/
│   ├── main.zig
│   └── lib.zig
└── tests/
    └── lib_test.zig

ファイル: part1/build.zig

const std = @import("std");

// TODO: build関数を実装
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // TODO: 静的ライブラリをビルド (src/lib.zig)
    const lib = b.addStaticLibrary(.{
        .name = "mathlib",
        .root_source_file = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(lib);

    // TODO: 実行ファイルをビルド (src/main.zig)
    // ライブラリをリンクする
    const exe = b.addExecutable(.{
        .name = "calculator",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    exe.linkLibrary(lib);
    b.installArtifact(exe);

    // TODO: 実行ステップを追加
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

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

    // TODO: テストステップを追加
    const lib_tests = b.addTest(.{
        .root_source_file = b.path("tests/lib_test.zig"),
        .target = target,
        .optimize = optimize,
    });

    const run_tests = b.addRunArtifact(lib_tests);

    const test_step = b.step("test", "Run library tests");
    test_step.dependOn(&run_tests.step);
}

ファイル: part1/src/lib.zig

// TODO: 数学関数ライブラリを実装
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn subtract(a: i32, b: i32) i32 {
    return a - b;
}

pub fn multiply(a: i32, b: i32) i32 {
    return a * b;
}

pub fn divide(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivisionByZero;
    return @divTrunc(a, b);
}

ファイル: part1/src/main.zig

const std = @import("std");

pub fn main() !void {
    // TODO: ライブラリを使用して簡単な計算を実行
    std.debug.print("Calculator Demo\n", .{});
    std.debug.print("2 + 3 = {}\n", .{add(2, 3)});
    std.debug.print("5 - 3 = {}\n", .{subtract(5, 3)});
    std.debug.print("4 * 5 = {}\n", .{multiply(4, 5)});
    std.debug.print("10 / 2 = {}\n", .{try divide(10, 2)});
}

// lib.zigの関数をインポート
extern fn add(a: i32, b: i32) i32;
extern fn subtract(a: i32, b: i32) i32;
extern fn multiply(a: i32, b: i32) i32;
extern fn divide(a: i32, b: i32) i32;

ファイル: part1/tests/lib_test.zig

const std = @import("std");

// TODO: ライブラリのテストを実装
test "add" {
    try std.testing.expectEqual(@as(i32, 5), add(2, 3));
    try std.testing.expectEqual(@as(i32, 0), add(-2, 2));
}

test "subtract" {
    try std.testing.expectEqual(@as(i32, 2), subtract(5, 3));
    try std.testing.expectEqual(@as(i32, -4), subtract(-2, 2));
}

test "multiply" {
    try std.testing.expectEqual(@as(i32, 20), multiply(4, 5));
    try std.testing.expectEqual(@as(i32, -20), multiply(-4, 5));
}

test "divide" {
    try std.testing.expectEqual(@as(i32, 5), try divide(10, 2));
    try std.testing.expectError(error.DivisionByZero, divide(10, 0));
}

extern fn add(a: i32, b: i32) i32;
extern fn subtract(a: i32, b: i32) i32;
extern fn multiply(a: i32, b: i32) i32;
extern fn divide(a: i32, b: i32) anyerror!i32;

Part 2: カスタムビルドオプション (20点)

ビルドオプションを追加し、コードから利用できるようにしてください。

ファイル: part2/build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // TODO: カスタムビルドオプションを追加
    const enable_logging = b.option(bool, "enable-logging", "Enable logging") orelse false;
    const log_level = b.option(enum { Debug, Info, Warning, Error }, "log-level", "Log level") orelse .Info;
    const app_name = b.option([]const u8, "app-name", "Application name") orelse "MyApp";
    const version = b.option([]const u8, "version", "Application version") orelse "1.0.0";

    const exe = b.addExecutable(.{
        .name = "myapp",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // TODO: ビルドオプションをコードに渡す
    const options = b.addOptions();
    options.addOption(bool, "enable_logging", enable_logging);
    options.addOption(@TypeOf(log_level), "log_level", log_level);
    options.addOption([]const u8, "app_name", app_name);
    options.addOption([]const u8, "version", version);

    exe.root_module.addOptions("config", options);

    b.installArtifact(exe);

    // 実行ステップ
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

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

ファイル: part2/src/main.zig

const std = @import("std");
const config = @import("config");

pub fn main() !void {
    std.debug.print("=== {} v{s} ===\n\n", .{config.app_name, config.version});

    if (config.enable_logging) {
        std.debug.print("Logging enabled\n", .{});
        std.debug.print("Log level: {}\n\n", .{config.log_level});
    }

    std.debug.print("Application running...\n", .{});
}

実行例:

# デフォルト設定
zig build run

# カスタム設定
zig build run -Denable-logging=true -Dlog-level=Debug -Dapp-name="SuperApp" -Dversion="2.0.0"

Part 3: マルチターゲットビルド (20点)

複数のプラットフォーム向けにビルドするbuild.zigを作成してください。

ファイル: part3/build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});

    // TODO: 複数のターゲット向けにビルド
    const targets = [_]std.Target.Query{
        .{ .cpu_arch = .x86_64, .os_tag = .linux },
        .{ .cpu_arch = .x86_64, .os_tag = .windows },
        .{ .cpu_arch = .aarch64, .os_tag = .macos },
        .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
    };

    const build_all_step = b.step("build-all", "Build for all targets");

    for (targets) |target_query| {
        const resolved_target = b.resolveTargetQuery(target_query);

        const exe = b.addExecutable(.{
            .name = "multiplatform",
            .root_source_file = b.path("src/main.zig"),
            .target = resolved_target,
            .optimize = optimize,
        });

        // TODO: ターゲット別のディレクトリにインストール
        const target_triple = try resolved_target.result.zigTriple(b.allocator);

        const install = b.addInstallArtifact(exe, .{
            .dest_dir = .{
                .override = .{
                    .custom = target_triple,
                },
            },
        });

        build_all_step.dependOn(&install.step);
    }

    // デフォルトターゲット
    const target = b.standardTargetOptions(.{});
    const exe = b.addExecutable(.{
        .name = "multiplatform",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    const run_step = b.step("run", "Run for native target");
    run_step.dependOn(&run_cmd.step);
}

ファイル: part3/src/main.zig

const std = @import("std");
const builtin = @import("builtin");

pub fn main() !void {
    std.debug.print("=== Multiplatform App ===\n", .{});
    std.debug.print("OS: {}\n", .{builtin.os.tag});
    std.debug.print("Arch: {}\n", .{builtin.cpu.arch});
    std.debug.print("Endian: {}\n", .{builtin.cpu.arch.endian()});
}

実行例:

# すべてのターゲット向けにビルド
zig build build-all

# 特定のターゲット向けにビルド
zig build -Dtarget=x86_64-windows
zig build -Dtarget=aarch64-macos

Part 4: カスタムビルドステップ (20点)

カスタムビルドステップを追加してビルドプロセスを拡張してください。

ファイル: part4/build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // TODO: バージョンファイルを生成
    const version_step = b.addWriteFiles();
    const version_content = b.fmt("1.0.0-{}", .{std.time.timestamp()});
    const version_file = version_step.add("version.txt", version_content);

    // TODO: 設定ファイルを生成
    const config_step = b.addWriteFiles();
    const config_content =
        \\{
        \\  "name": "MyApp",
        \\  "debug": true,
        \\  "max_connections": 100
        \\}
    ;
    const config_file = config_step.add("config.json", config_content);

    const exe = b.addExecutable(.{
        .name = "myapp",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // ビルド前にファイル生成
    exe.step.dependOn(&version_step.step);
    exe.step.dependOn(&config_step.step);

    b.installArtifact(exe);
    b.installFile(version_file, "version.txt");
    b.installFile(config_file, "config.json");

    // TODO: フォーマットステップ
    const fmt_step = b.addFmt(.{
        .paths = &[_][]const u8{ "src", "build.zig" },
        .check = false,
    });

    const fmt_cmd = b.step("fmt", "Format source code");
    fmt_cmd.dependOn(&fmt_step.step);

    // TODO: クリーンステップ
    const clean_step = b.step("clean", "Remove build artifacts");
    const rm_cmd = b.addRemoveDirTree(b.install_path);
    clean_step.dependOn(&rm_cmd.step);

    // 実行ステップ
    const run_cmd = b.addRunArtifact(exe);
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

ファイル: part4/src/main.zig

const std = @import("std");

pub fn main() !void {
    std.debug.print("Application started\n", .{});
    std.debug.print("Version file and config generated during build\n", .{});
}

実行例:

# ビルド
zig build

# フォーマット
zig build fmt

# クリーン
zig build clean

ボーナス課題 (20点)

Bonus 1: パッケージシステム (10点)

複数のモジュールを持つプロジェクトを作成してください。

ディレクトリ構造:

bonus1/
├── build.zig
├── src/
│   └── main.zig
├── modules/
│   ├── utils/
│   │   └── string.zig
│   └── math/
│       └── calc.zig

ファイル: bonus1/build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // TODO: モジュールを追加
    const utils_module = b.addModule("utils", .{
        .root_source_file = b.path("modules/utils/string.zig"),
    });

    const math_module = b.addModule("math", .{
        .root_source_file = b.path("modules/math/calc.zig"),
    });

    const exe = b.addExecutable(.{
        .name = "modular_app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // モジュールをインポート
    exe.root_module.addImport("utils", utils_module);
    exe.root_module.addImport("math", math_module);

    b.installArtifact(exe);

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

ファイル: bonus1/modules/utils/string.zig

const std = @import("std");

// TODO: 文字列ユーティリティ関数
pub fn reverse(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    const result = try allocator.alloc(u8, str.len);
    for (str, 0..) |c, i| {
        result[str.len - 1 - i] = c;
    }
    return result;
}

pub fn uppercase(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    const result = try allocator.alloc(u8, str.len);
    for (str, 0..) |c, i| {
        result[i] = if (c >= 'a' and c <= 'z') c - 32 else c;
    }
    return result;
}

ファイル: bonus1/modules/math/calc.zig

// TODO: 数学関数
pub fn factorial(n: u32) u64 {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}

pub fn fibonacci(n: u32) u64 {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

ファイル: bonus1/src/main.zig

const std = @import("std");
const utils = @import("utils");
const math = @import("math");

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Modular Application ===\n\n", .{});

    // String utilities
    const reversed = try utils.reverse(allocator, "hello");
    defer allocator.free(reversed);
    std.debug.print("Reversed: {s}\n", .{reversed});

    const upper = try utils.uppercase(allocator, "hello");
    defer allocator.free(upper);
    std.debug.print("Uppercase: {s}\n\n", .{upper});

    // Math functions
    std.debug.print("Factorial(5) = {}\n", .{math.factorial(5)});
    std.debug.print("Fibonacci(10) = {}\n", .{math.fibonacci(10)});
}

Bonus 2: 完全なプロジェクトテンプレート (10点)

本番環境で使用できる完全なbuild.zigテンプレートを作成してください。

ファイル: bonus2/build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // ビルドオプション
    const enable_tests = b.option(bool, "enable-tests", "Enable tests") orelse true;
    const enable_docs = b.option(bool, "enable-docs", "Enable documentation") orelse false;
    const enable_benchmarks = b.option(bool, "enable-benchmarks", "Enable benchmarks") orelse false;

    // ライブラリのビルド
    const lib = b.addStaticLibrary(.{
        .name = "production_lib",
        .root_source_file = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(lib);

    // 実行ファイルのビルド
    const exe = b.addExecutable(.{
        .name = "production_app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    exe.linkLibrary(lib);
    b.installArtifact(exe);

    // 実行ステップ
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

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

    // テスト
    if (enable_tests) {
        const lib_tests = b.addTest(.{
            .root_source_file = b.path("src/lib.zig"),
            .target = target,
            .optimize = optimize,
        });

        const run_lib_tests = b.addRunArtifact(lib_tests);

        const test_step = b.step("test", "Run unit tests");
        test_step.dependOn(&run_lib_tests.step);
    }

    // ドキュメント
    if (enable_docs) {
        const docs_cmd = b.addSystemCommand(&[_][]const u8{
            "zig", "test", "-femit-docs", "src/lib.zig",
        });

        const docs_step = b.step("docs", "Generate documentation");
        docs_step.dependOn(&docs_cmd.step);
    }

    // ベンチマーク
    if (enable_benchmarks) {
        const bench_exe = b.addExecutable(.{
            .name = "benchmark",
            .root_source_file = b.path("benchmark/main.zig"),
            .target = target,
            .optimize = .ReleaseFast,
        });

        const run_bench = b.addRunArtifact(bench_exe);

        const bench_step = b.step("bench", "Run benchmarks");
        bench_step.dependOn(&run_bench.step);
    }

    // フォーマット
    const fmt_step = b.addFmt(.{
        .paths = &[_][]const u8{ "src", "benchmark", "build.zig" },
    });

    const fmt_cmd = b.step("fmt", "Format source code");
    fmt_cmd.dependOn(&fmt_step.step);

    // クリーン
    const clean_step = b.step("clean", "Remove build artifacts");
    const rm_cmd = b.addRemoveDirTree(b.install_path);
    clean_step.dependOn(&rm_cmd.step);
}

評価基準

項目 配点
Part 1: 基本的なbuild.zigの作成 20点
Part 2: カスタムビルドオプション 20点
Part 3: マルチターゲットビルド 20点
Part 4: カスタムビルドステップ 20点
**マンダトリー合計** **80点**
Bonus 1: パッケージシステム 10点
Bonus 2: 完全なプロジェクトテンプレート 10点
**ボーナス合計** **20点**

合格基準

  • マンダトリー: 64点以上で合格(80点満点の80%)
  • ボーナス: 追加評価(最終成績の加算)
  • 実行例

    # Part 1
    cd part1
    zig build
    zig build run
    zig build test
    
    # Part 2
    cd part2
    zig build run -Denable-logging=true -Dapp-name="MyApp"
    
    # Part 3
    cd part3
    zig build build-all
    zig build -Dtarget=x86_64-windows
    
    # Part 4
    cd part4
    zig build
    zig build fmt
    zig build clean
    
    # Bonus 1
    cd bonus1
    zig build run
    
    # Bonus 2
    cd bonus2
    zig build test
    zig build docs -Denable-docs=true
    zig build bench -Denable-benchmarks=true
    

    参考資料

  • Zig Language Reference - Build System: https://ziglang.org/documentation/master/#Build-System
  • Zig Build System: https://ziglang.org/learn/build-system/
  • Ziglearn - Build System: https://ziglearn.org/chapter-3/