summaryrefslogtreecommitdiff
path: root/introspect/build.zig
blob: 6662151fb602387339bddd9b4e9636a651b14a7c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Build fragment for introspect: the 9P debug/introspection library (module
//! `introspect`), its freestanding check, the `introspect` demo server and the
//! test suites under introspect/test. It is `@import`ed by the root build.zig
//! and called with the root builder, so every `b.path(...)` here is relative
//! to the cloud9 root (hence the `introspect/` prefix), every option is
//! defined by the root (no `standardTargetOptions` here) and every step it
//! registers lands in the root's step list under the `introspect` prefix.
//!
//! Steps: introspect, introspect-test, introspect-check-freestanding,
//! introspect-debug-test, introspect-debug-itest, introspect-adv.
const std = @import("std");

/// What the root passes in. The root owns target/optimize resolution and the
/// cloud9 module; this fragment derives everything else from them.
pub const Context = struct {
    target: std.Build.ResolvedTarget,
    optimize: std.builtin.OptimizeMode,
    /// The cloud9 library module for `target`. Its `root_source_file` is also
    /// used to instantiate cloud9 for the freestanding check target.
    cloud9: *std.Build.Module,
};

pub const Artifacts = struct {
    /// The `introspect` module, exported with `b.addModule` so dependents of
    /// cloud9 can `.module("introspect")`. Built for any target; the Linux
    /// layer is compiled in only when the target OS is Linux.
    module: *std.Build.Module,
    /// The demo 9P2000 server (binary `introspect`); null when the target is
    /// not Linux. 9player's integration tests use it as their server.
    demo: ?*std.Build.Step.Compile,
    /// `introspect-test`: library (and demo) unit tests.
    test_step: *std.Build.Step,
    /// `introspect-check-freestanding`: the core compiled for riscv32-freestanding-none.
    check_step: *std.Build.Step,
    /// `introspect-debug-test`: linux/debug.zig unit tests; null when not Linux.
    debug_test_step: ?*std.Build.Step,
    /// `introspect-adv`: the hostile-client suites are attached by `add`, the
    /// Linux-layer suite (which needs a 9player mount) by `addPlayerTests`.
    adv_step: *std.Build.Step,
};

pub fn add(b: *std.Build, ctx: Context) Artifacts {
    const target = ctx.target;
    const optimize = ctx.optimize;
    const is_linux = target.result.os.tag == .linux;

    // Build-time facts embedded into the demo (/build/*): jj change id, UTC
    // time, optimize mode, target triple. `jj` is pointed at the build root
    // (the cloud9 checkout) explicitly, so the result does not depend on the
    // directory `zig build` was invoked from.
    const build_options = b.addOptions();
    var code: u8 = 0;
    const repo = b.build_root.path orelse ".";
    const change_id = b.runAllowFail(&.{ "jj", "-R", repo, "log", "--no-graph", "-r", "@", "-T", "change_id.short()", "--ignore-working-copy" }, &code, .ignore) catch "unknown";
    build_options.addOption([]const u8, "change_id", std.mem.trim(u8, change_id, " \n\r\t"));
    const stamp = b.runAllowFail(&.{ "date", "-u", "+%Y-%m-%dT%H:%M:%SZ" }, &code, .ignore) catch "unknown";
    build_options.addOption([]const u8, "build_time", std.mem.trim(u8, stamp, " \n\r\t"));
    build_options.addOption([]const u8, "optimize", @tagName(optimize));
    build_options.addOption([]const u8, "target", target.result.zigTriple(b.allocator) catch "unknown");

    // The library: freestanding core + vars, scratch (allocator), Linux layer.
    // Exported under the name `introspect` for packages that depend on cloud9.
    const lib_mod = b.addModule("introspect", .{
        .root_source_file = b.path("introspect/src/root.zig"),
        .target = target,
        .optimize = optimize,
        .imports = &.{.{ .name = "cloud9", .module = ctx.cloud9 }},
    });

    const test_step = b.step("introspect-test", "Run the introspect library's unit tests (and the demo's)");
    test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = lib_mod })).step);

    // The core must compile without an OS (rule 1 of introspect/docs/LIBRARY.md).
    // cloud9 is re-instantiated for that target from the same root source.
    const fs_target = b.resolveTargetQuery(.{ .cpu_arch = .riscv32, .os_tag = .freestanding, .abi = .none });
    const fs_cloud9 = b.createModule(.{
        .root_source_file = ctx.cloud9.root_source_file.?,
        .target = fs_target,
        .optimize = optimize,
    });
    const fs_check = b.addObject(.{
        .name = "introspect-freestanding",
        .root_module = b.createModule(.{
            .root_source_file = b.path("introspect/src/freestanding_check.zig"),
            .target = fs_target,
            .optimize = optimize,
            .imports = &.{.{ .name = "cloud9", .module = fs_cloud9 }},
        }),
    });
    const check_step = b.step("introspect-check-freestanding", "Compile the introspect core for riscv32-freestanding-none");
    check_step.dependOn(&fs_check.step);

    // `introspect-adv` exists on every target so the step list is stable; its
    // suites are attached below (Linux only) and by addPlayerTests.
    const adv_step = b.step("introspect-adv", "Run introspect's adversarial suites (hostile clients, Linux layer; several minutes)");

    if (!is_linux) {
        adv_step.dependOn(&b.addFail("introspect-adv needs a Linux target (the demo server is Linux-only)").step);
        return .{ .module = lib_mod, .demo = null, .test_step = test_step, .check_step = check_step, .debug_test_step = null, .adv_step = adv_step };
    }

    // The demo: a 9P2000 server whose tree is the binary itself (build-time,
    // comptime and runtime facts, a worker thread, the Linux debug layer).
    const demo_mod = b.createModule(.{
        .root_source_file = b.path("introspect/demo/main.zig"),
        .target = target,
        .optimize = optimize,
        .imports = &.{
            .{ .name = "cloud9", .module = ctx.cloud9 },
            .{ .name = "build_options", .module = build_options.createModule() },
            .{ .name = "introspect", .module = lib_mod },
        },
    });
    const demo = b.addExecutable(.{ .name = "introspect", .root_module = demo_mod });
    const install_demo = b.addInstallArtifact(demo, .{});
    b.getInstallStep().dependOn(&install_demo.step);
    b.step("introspect", "Build and install only the introspect demo server").dependOn(&install_demo.step);
    test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = demo_mod })).step);

    // Linux debug facilities (threads, stacks, breakpoints, panic): self-contained unit tests.
    const debug_mod = b.createModule(.{
        .root_source_file = b.path("introspect/src/linux/debug.zig"),
        .target = target,
        .optimize = optimize,
    });
    const debug_test_step = b.step("introspect-debug-test", "Run the introspect/src/linux/debug.zig unit tests");
    debug_test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = debug_mod })).step);

    // Hostile raw-9P clients against the demo (framing, tags, floods; the core's
    // /vars tree, snapshots, fid table). `--fast` as in the umbrella script.
    inline for (.{ "adv_introspect_hostile", "adv_core_hostile" }) |suite| {
        const run = b.addSystemCommand(&.{"bash"});
        run.addFileArg(b.path("introspect/test/" ++ suite ++ ".sh"));
        run.addArtifactArg(demo);
        run.addArg("--fast");
        adv_step.dependOn(&run.step);
    }

    return .{ .module = lib_mod, .demo = demo, .test_step = test_step, .check_step = check_step, .debug_test_step = debug_test_step, .adv_step = adv_step };
}

/// The suites that drive the demo through a 9player mount: test/debug.sh
/// (threads, stacks, breakpoints, panic end to end) and
/// test/adv_linux_probe.sh (memory endpoints, signal machinery, poll loop).
/// Called by the root after the 9player fragment; `player` is null when
/// 9player is disabled, in which case the steps exist but fail with a notice.
/// Returns the `introspect-debug-itest` step (null when the target is not Linux).
pub fn addPlayerTests(b: *std.Build, arts: Artifacts, player: ?*std.Build.Step.Compile) ?*std.Build.Step {
    const demo = arts.demo orelse return null; // not Linux: nothing to drive
    const debug_itest = b.step("introspect-debug-itest", "Run introspect/test/debug.sh (threads, stacks, breakpoints, panic through 9player)");
    const exe = player orelse {
        const fail = b.addFail("introspect-debug-itest and the Linux-layer adversarial suite need 9player (build with -D9player=true)");
        debug_itest.dependOn(&fail.step);
        arts.adv_step.dependOn(&fail.step);
        return debug_itest;
    };
    const dbg = b.addSystemCommand(&.{"bash"});
    dbg.addFileArg(b.path("introspect/test/debug.sh"));
    dbg.addArtifactArg(exe);
    dbg.addArtifactArg(demo);
    debug_itest.dependOn(&dbg.step);

    const adv_linux = b.addSystemCommand(&.{"bash"});
    adv_linux.addFileArg(b.path("introspect/test/adv_linux_probe.sh"));
    adv_linux.addArtifactArg(exe);
    adv_linux.addArtifactArg(demo);
    arts.adv_step.dependOn(&adv_linux.step);
    return debug_itest;
}