const std = @import("std"); const builtin = @import("builtin"); const headers = &[_][]const u8{ "common.h", "ast.h", "parser.h", "zir.h", "astgen.h", }; const c_lib_files = &[_][]const u8{ "tokenizer.c", "ast.c", "zig0.c", "parser.c", "zir.c", "astgen.c", }; const all_c_files = c_lib_files ++ &[_][]const u8{"main.c"}; const cflags = &[_][]const u8{ "-std=c11", "-Wall", "-Wvla", "-Wextra", "-Werror", "-Wshadow", "-Wswitch", "-Walloca", "-Wformat=2", "-fno-common", "-Wconversion", "-Wuninitialized", "-Wdouble-promotion", "-fstack-protector-all", "-Wimplicit-fallthrough", "-Wno-unused-function", // TODO remove once refactoring is done //"-D_FORTIFY_SOURCE=2", // consider when optimization flags are enabled }; const compilers = &[_][]const u8{ "zig", "clang", "gcc", "tcc" }; pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const cc = b.option([]const u8, "cc", "C compiler") orelse "zig"; const no_exec = b.option(bool, "no-exec", "Compile test binary without running it") orelse false; const valgrind = b.option(bool, "valgrind", "Run tests under valgrind") orelse false; const test_timeout = b.option([]const u8, "test-timeout", "Test execution timeout (default: 10s, none with valgrind)"); const target = blk: { var query = b.standardTargetOptionsQueryOnly(.{}); if (valgrind) { const arch = query.cpu_arch orelse builtin.cpu.arch; if (arch == .x86_64) { query.cpu_features_sub.addFeature(@intFromEnum(std.Target.x86.Feature.avx512f)); } } break :blk b.resolveTargetQuery(query); }; const test_step = b.step("test", "Run unit tests"); addTestStep(b, test_step, target, optimize, cc, no_exec, valgrind, test_timeout); const fmt_step = b.step("fmt", "clang-format"); const clang_format = b.addSystemCommand(&.{ "clang-format", "-i" }); for (all_c_files ++ headers) |f| clang_format.addFileArg(b.path(f)); fmt_step.dependOn(&clang_format.step); const lint_step = b.step("lint", "Run linters"); for (all_c_files) |cfile| { const clang_analyze = b.addSystemCommand(&.{ "clang", "--analyze", "--analyzer-output", "text", "-Wno-unused-command-line-argument", "-Werror", // false positive in astgen.c comptimeDecl: analyzer cannot track // scratch_instructions ownership through pointer parameters. "-Xclang", "-analyzer-disable-checker", "-Xclang", "unix.Malloc", }); clang_analyze.addFileArg(b.path(cfile)); clang_analyze.expectExitCode(0); lint_step.dependOn(&clang_analyze.step); // TODO(motiejus) re-enable once project // nears completion. Takes too long for comfort. //const gcc_analyze = b.addSystemCommand(&.{ // "gcc", // "-c", // "--analyzer", // "-Werror", // "-o", // "/dev/null", //}); //gcc_analyze.addFileArg(b.path(cfile)); //gcc_analyze.expectExitCode(0); //lint_step.dependOn(&gcc_analyze.step); const cppcheck = b.addSystemCommand(&.{ "cppcheck", "--quiet", "--error-exitcode=1", "--check-level=exhaustive", "--enable=all", "--inline-suppr", "--suppress=missingIncludeSystem", "--suppress=checkersReport", "--suppress=unusedFunction", // TODO remove after plumbing is done "--suppress=unusedStructMember", // TODO remove after plumbing is done "--suppress=unmatchedSuppression", }); cppcheck.addFileArg(b.path(cfile)); cppcheck.expectExitCode(0); lint_step.dependOn(&cppcheck.step); } const fmt_check = b.addSystemCommand(&.{ "clang-format", "--dry-run", "-Werror" }); for (all_c_files ++ headers) |f| fmt_check.addFileArg(b.path(f)); fmt_check.expectExitCode(0); b.default_step.dependOn(&fmt_check.step); for (compilers) |compiler| { addTestStep(b, b.default_step, target, optimize, compiler, false, valgrind, test_timeout); } const all_step = b.step("all", "Run fmt check, lint, and tests with all compilers"); all_step.dependOn(b.default_step); all_step.dependOn(lint_step); } fn addTestStep( b: *std.Build, step: *std.Build.Step, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, cc: []const u8, no_exec: bool, valgrind: bool, test_timeout: ?[]const u8, ) void { const test_mod = b.createModule(.{ .root_source_file = b.path("test_all.zig"), .optimize = optimize, .target = target, }); test_mod.addIncludePath(b.path(".")); // TODO(zig 0.16+): remove this if block entirely; keep only the addLibrary branch. // Also delete addCObjectsDirectly. // Zig 0.15's ELF archive parser fails on archives containing odd-sized objects // (off-by-one after 2-byte alignment). This is fixed on zig master/0.16. if (comptime builtin.zig_version.order(.{ .major = 0, .minor = 16, .patch = 0 }) == .lt) { addCObjectsDirectly(b, test_mod, cc, optimize); } else { const lib_mod = b.createModule(.{ .optimize = optimize, .target = target, .link_libc = true, }); const lib = b.addLibrary(.{ .name = b.fmt("zig0-{s}", .{cc}), .root_module = lib_mod, }); addCSources(b, lib.root_module, cc, optimize); test_mod.linkLibrary(lib); } const test_exe = b.addTest(.{ .root_module = test_mod, .use_llvm = false, .use_lld = false, }); const timeout: ?[]const u8 = test_timeout orelse if (valgrind) null else "10"; if (valgrind) { if (timeout) |t| test_exe.setExecCmd(&.{ "timeout", t, "valgrind", "--error-exitcode=2", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--track-fds=yes", null, }) else test_exe.setExecCmd(&.{ "valgrind", "--error-exitcode=2", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--track-fds=yes", null, }); } else { test_exe.setExecCmd(&.{ "timeout", timeout orelse "10", null }); } if (no_exec) { const install = b.addInstallArtifact(test_exe, .{}); step.dependOn(&install.step); } else { step.dependOn(&b.addRunArtifact(test_exe).step); } } fn addCSources( b: *std.Build, mod: *std.Build.Module, cc: []const u8, optimize: std.builtin.OptimizeMode, ) void { if (std.mem.eql(u8, cc, "zig")) { mod.addCSourceFiles(.{ .files = c_lib_files, .flags = cflags }); } else for (c_lib_files) |cfile| { const cc1 = b.addSystemCommand(&.{cc}); cc1.addArgs(cflags ++ .{"-g"}); cc1.addArg(switch (optimize) { .Debug => "-O0", .ReleaseFast, .ReleaseSafe => "-O3", .ReleaseSmall => "-Os", }); cc1.addArg("-c"); cc1.addFileArg(b.path(cfile)); cc1.addArg("-o"); mod.addObjectFile(cc1.addOutputFileArg(b.fmt("{s}.o", .{cfile[0 .. cfile.len - 2]}))); } } // TODO(zig 0.16+): delete this function. fn addCObjectsDirectly( b: *std.Build, mod: *std.Build.Module, cc: []const u8, optimize: std.builtin.OptimizeMode, ) void { addCSources(b, mod, cc, optimize); mod.linkSystemLibrary("c", .{}); }