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 target = b.standardTargetOptions(.{}); 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_step = b.step("test", "Run unit tests"); addTestStep(b, test_step, target, optimize, cc, no_exec, valgrind); 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"); const clang_analyze = b.addSystemCommand(&.{ "clang", "--analyze", "--analyzer-output", "text", "-Wno-unused-command-line-argument", "-Werror", }); for (all_c_files) |cfile| clang_analyze.addFileArg(b.path(cfile)); clang_analyze.expectExitCode(0); lint_step.dependOn(&clang_analyze.step); const gcc_analyze = b.addSystemCommand(&.{ "gcc", "--analyzer", "-Werror", "-o", "/dev/null", }); for (all_c_files) |cfile| 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", "--suppress=missingIncludeSystem", "--suppress=checkersReport", "--suppress=unusedFunction", // TODO remove after plumbing is done "--suppress=unusedStructMember", // TODO remove after plumbing is done }); for (all_c_files) |cfile| cppcheck.addFileArg(b.path(cfile)); cppcheck.expectExitCode(0); lint_step.dependOn(&cppcheck.step); const all_step = b.step("all", "Run fmt check, lint, and tests with all compilers"); all_step.dependOn(lint_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); all_step.dependOn(&fmt_check.step); for (compilers) |compiler| { addTestStep(b, all_step, target, optimize, compiler, false, valgrind); } b.default_step = all_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, ) 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 }); if (valgrind) { test_exe.setExecCmd(&.{ "valgrind", "--error-exitcode=2", 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", .{}); }