diff --git a/build.zig b/build.zig index 3a217c6..e77c393 100644 --- a/build.zig +++ b/build.zig @@ -1,15 +1,72 @@ const std = @import("std"); +const afl = @import("zig-afl-kit"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const version = getVersion(b); + const scripty = b.dependency("scripty", .{}); const superhtml = b.addModule("superhtml", .{ .root_source_file = b.path("src/root.zig"), }); superhtml.addImport("scripty", scripty.module("scripty")); + const options = b.addOptions(); + const verbose_logging = b.option(bool, "log", "Enable verbose logging also in release modes") orelse false; + const scopes = b.option([]const []const u8, "scope", "Enable this scope (all scopes are enabled when none is specified through this option), can be used multiple times") orelse &[0][]const u8{}; + options.addOption(bool, "verbose_logging", verbose_logging); + options.addOption([]const []const u8, "enabled_scopes", scopes); + options.addOption([]const u8, "version", version.string()); + options.addOption(Version.Kind, "version_kind", version); + + const folders = b.dependency("known-folders", .{}); + const lsp = b.dependency("zig-lsp-kit", .{}); + + setupCliTool(b, target, optimize, options, superhtml, folders, lsp); + setupWasmStep(b, optimize, options, superhtml, lsp); + setupFuzzStep(b, target, superhtml); + setupCheckStep(b, target, optimize, options, superhtml, folders, lsp); + if (version == .tag) { + setupReleaseStep(b, options, superhtml, folders, lsp); + } +} + +fn setupCheckStep( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + options: *std.Build.Step.Options, + superhtml: *std.Build.Module, + folders: *std.Build.Dependency, + lsp: *std.Build.Dependency, +) void { + const super_cli_check = b.addExecutable(.{ + .name = "superhtml", + .root_source_file = b.path("src/cli.zig"), + .target = target, + .optimize = optimize, + }); + + super_cli_check.root_module.addImport("superhtml", superhtml); + super_cli_check.root_module.addImport( + "known-folders", + folders.module("known-folders"), + ); + super_cli_check.root_module.addImport("lsp", lsp.module("lsp")); + super_cli_check.root_module.addOptions("build_options", options); + + const check = b.step("check", "Check if the SuperHTML CLI compiles"); + check.dependOn(&super_cli_check.step); +} +fn setupTestStep( + b: *std.Build, + target: std.Build.ResolvedTarget, + superhtml: *std.Build.Module, +) void { + const test_step = b.step("test", "Run unit tests"); + const unit_tests = b.addTest(.{ .root_source_file = b.path("src/root.zig"), .target = target, @@ -21,9 +78,77 @@ pub fn build(b: *std.Build) !void { unit_tests.root_module.addImport("superhtml", superhtml); const run_unit_tests = b.addRunArtifact(unit_tests); - const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_unit_tests.step); + const fuzz_tests = b.addTest(.{ + .root_source_file = b.path("src/fuzz.zig"), + .target = target, + .optimize = .Debug, + // .strip = true, + // .filter = "nesting", + }); + + fuzz_tests.root_module.addImport("superhtml", superhtml); + const run_fuzz_tests = b.addRunArtifact(fuzz_tests); + test_step.dependOn(&run_fuzz_tests.step); +} + +fn setupFuzzStep( + b: *std.Build, + target: std.Build.ResolvedTarget, + superhtml: *std.Build.Module, +) void { + const fuzz = b.step( + "fuzz", + "Generate an executable for AFL++ (persistent mode) plus extra tooling", + ); + const afl_obj = b.addObject(.{ + .name = "superfuzz-afl", + .root_source_file = b.path("src/fuzz/afl.zig"), + .target = target, + .optimize = .Debug, + .single_threaded = true, + }); + + afl_obj.root_module.addImport("superhtml", superhtml); + afl_obj.root_module.stack_check = false; // not linking with compiler-rt + afl_obj.root_module.link_libc = true; // afl runtime depends on libc + + const afl_fuzz = afl.addInstrumentedExe(b, afl_obj); + fuzz.dependOn(&b.addInstallFile(afl_fuzz, "superfuzz-afl").step); + + const super_fuzz = b.addExecutable(.{ + .name = "superfuzz", + .root_source_file = b.path("src/fuzz.zig"), + .target = target, + .optimize = .Debug, + .single_threaded = true, + }); + + super_fuzz.root_module.addImport("superhtml", superhtml); + fuzz.dependOn(&b.addInstallArtifact(super_fuzz, .{}).step); + + const supergen = b.addExecutable(.{ + .name = "supergen", + .root_source_file = b.path("src/fuzz/astgen.zig"), + .target = target, + .optimize = .Debug, + .single_threaded = true, + }); + + supergen.root_module.addImport("superhtml", superhtml); + fuzz.dependOn(&b.addInstallArtifact(supergen, .{}).step); +} + +fn setupCliTool( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + options: *std.Build.Step.Options, + superhtml: *std.Build.Module, + folders: *std.Build.Dependency, + lsp: *std.Build.Dependency, +) void { const super_cli = b.addExecutable(.{ .name = "superhtml", .root_source_file = b.path("src/cli.zig"), @@ -32,15 +157,6 @@ pub fn build(b: *std.Build) !void { .single_threaded = true, }); - const verbose_logging = b.option(bool, "log", "Enable verbose logging also in release modes") orelse false; - const scopes = b.option([]const []const u8, "scope", "Enable this scope (all scopes are enabled when none is specified through this option), can be used multiple times") orelse &[0][]const u8{}; - const options = b.addOptions(); - options.addOption(bool, "verbose_logging", verbose_logging); - options.addOption([]const []const u8, "enabled_scopes", scopes); - - const folders = b.dependency("known-folders", .{}); - const lsp = b.dependency("zig-lsp-kit", .{}); - super_cli.root_module.addImport("superhtml", superhtml); super_cli.root_module.addImport( "known-folders", @@ -55,26 +171,49 @@ pub fn build(b: *std.Build) !void { run_exe_step.dependOn(&run_exe.step); b.installArtifact(super_cli); +} - const super_cli_check = b.addExecutable(.{ +fn setupWasmStep( + b: *std.Build, + optimize: std.builtin.OptimizeMode, + options: *std.Build.Step.Options, + superhtml: *std.Build.Module, + lsp: *std.Build.Dependency, +) void { + const wasm = b.step("wasm", "Generate a WASM build of the SuperHTML LSP for VSCode"); + const super_wasm_lsp = b.addExecutable(.{ .name = "superhtml", - .root_source_file = b.path("src/cli.zig"), - .target = target, + .root_source_file = b.path("src/wasm.zig"), + .target = b.resolveTargetQuery(.{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + }), .optimize = optimize, + .single_threaded = true, + .link_libc = false, }); - super_cli_check.root_module.addImport("superhtml", superhtml); - super_cli_check.root_module.addImport( - "known-folders", - folders.module("known-folders"), - ); - super_cli_check.root_module.addImport("lsp", lsp.module("lsp")); - super_cli_check.root_module.addOptions("build_options", options); + super_wasm_lsp.root_module.addImport("superhtml", superhtml); + super_wasm_lsp.root_module.addImport("lsp", lsp.module("lsp")); + super_wasm_lsp.root_module.addOptions("build_options", options); - const check = b.step("check", "Check if the SuperHTML CLI compiles"); - check.dependOn(&super_cli_check.step); + const target_output = b.addInstallArtifact(super_wasm_lsp, .{ + .dest_dir = .{ .override = .{ .custom = "" } }, + }); + wasm.dependOn(&target_output.step); +} - const release_step = b.step("release", "Create releases for the SuperHTML CLI"); +fn setupReleaseStep( + b: *std.Build, + options: *std.Build.Step.Options, + superhtml: *std.Build.Module, + folders: *std.Build.Dependency, + lsp: *std.Build.Dependency, +) void { + const release_step = b.step( + "release", + "Create releases for the SuperHTML CLI", + ); const targets: []const std.Target.Query = &.{ .{ .cpu_arch = .aarch64, .os_tag = .macos }, .{ .cpu_arch = .aarch64, .os_tag = .linux }, @@ -105,103 +244,71 @@ pub fn build(b: *std.Build) !void { const target_output = b.addInstallArtifact(super_exe_release, .{ .dest_dir = .{ .override = .{ - .custom = try t.zigTriple(b.allocator), + .custom = t.zigTriple(b.allocator) catch unreachable, }, }, }); release_step.dependOn(&target_output.step); } +} - const super_wasm_lsp = b.addExecutable(.{ - .name = "superhtml", - .root_source_file = b.path("src/wasm.zig"), - .target = b.resolveTargetQuery(.{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - }), - .optimize = optimize, - .single_threaded = true, - .link_libc = false, - }); - - super_wasm_lsp.root_module.addImport("superhtml", superhtml); - super_wasm_lsp.root_module.addImport("lsp", lsp.module("lsp")); - super_wasm_lsp.root_module.addOptions("build_options", options); - - const wasm = b.step("wasm", "Generate a WASM build of the SuperHTML LSP for VSCode"); - const target_output = b.addInstallArtifact(super_wasm_lsp, .{ - .dest_dir = .{ .override = .{ .custom = "" } }, - }); - wasm.dependOn(&target_output.step); - - const afl_fuzz_name = b.fmt("superfuzz-afl{s}", .{target.result.exeFileExt()}); - const afl_fuzz = b.addStaticLibrary(.{ - .name = afl_fuzz_name, - .root_source_file = b.path("src/fuzz/afl.zig"), - // .target = b.resolveTargetQuery(.{ .ofmt = .c }), - .target = target, - .optimize = .Debug, - .single_threaded = true, - }); - - afl_fuzz.root_module.addImport("superhtml", superhtml); - afl_fuzz.root_module.stack_check = false; // not linking with compiler-rt - afl_fuzz.root_module.link_libc = true; // afl runtime depends on libc - _ = afl_fuzz.getEmittedBin(); // hack around build system bug - - const afl_clang_fast_path = b.findProgram( - &.{ "afl-clang-fast", "afl-clang" }, - if (b.option([]const u8, "afl-path", "Path to AFLplusplus")) |afl_path| - &.{afl_path} - else - &.{}, - ) catch "afl-clang-fast"; - - const fuzz = b.step("fuzz", "Generate an executable for AFL++ (persistent mode) plus extra tooling"); - const run_afl_clang_fast = b.addSystemCommand(&.{ - afl_clang_fast_path, - "-o", - }); - - const prog_exe = run_afl_clang_fast.addOutputFileArg(afl_fuzz_name); - run_afl_clang_fast.addFileArg(b.path("src/fuzz/afl.c")); - // run_afl_clang_fast.addFileArg(afl_fuzz.getEmittedBin()); - // run_afl_clang_fast.addArg("-I/Users/kristoff/zig/0.13.0/files/lib/"); - run_afl_clang_fast.addFileArg(afl_fuzz.getEmittedLlvmBc()); - fuzz.dependOn(&b.addInstallBinFile(prog_exe, afl_fuzz_name).step); - - const super_fuzz = b.addExecutable(.{ - .name = "superfuzz", - .root_source_file = b.path("src/fuzz.zig"), - .target = target, - .optimize = .Debug, - .single_threaded = true, - }); - - super_fuzz.root_module.addImport("superhtml", superhtml); - fuzz.dependOn(&b.addInstallArtifact(super_fuzz, .{}).step); - - const supergen = b.addExecutable(.{ - .name = "supergen", - .root_source_file = b.path("src/fuzz/astgen.zig"), - .target = target, - .optimize = .Debug, - .single_threaded = true, - }); +const Version = union(Kind) { + tag: []const u8, + commit: []const u8, + // not in a git repo + unknown, - supergen.root_module.addImport("superhtml", superhtml); - fuzz.dependOn(&b.addInstallArtifact(supergen, .{}).step); + pub const Kind = enum { tag, commit, unknown }; - const fuzz_tests = b.addTest(.{ - .root_source_file = b.path("src/fuzz.zig"), - .target = target, - .optimize = .Debug, - // .strip = true, - // .filter = "nesting", - }); + pub fn string(v: Version) []const u8 { + return switch (v) { + .tag, .commit => |tc| tc, + .unknown => "unknown", + }; + } +}; +fn getVersion(b: *std.Build) Version { + const git_path = b.findProgram(&.{"git"}, &.{}) catch return .unknown; + const git_describe = std.mem.trim( + u8, + b.run(&[_][]const u8{ + git_path, "-C", + b.build_root.path.?, "describe", + "--match", "*.*.*", + "--tags", + }), + " \n\r", + ); - fuzz_tests.root_module.addImport("superhtml", superhtml); - const run_fuzz_tests = b.addRunArtifact(fuzz_tests); - test_step.dependOn(&run_fuzz_tests.step); + switch (std.mem.count(u8, git_describe, "-")) { + 0 => return .{ .tag = git_describe }, + 2 => { + // Untagged development build (e.g. 0.8.0-684-gbbe2cca1a). + var it = std.mem.split(u8, git_describe, "-"); + const tagged_ancestor = it.next() orelse unreachable; + const commit_height = it.next() orelse unreachable; + const commit_id = it.next() orelse unreachable; + + // Check that the commit hash is prefixed with a 'g' + // (it's a Git convention) + if (commit_id.len < 1 or commit_id[0] != 'g') { + std.debug.panic("Unexpected `git describe` output: {s}\n", .{git_describe}); + } + + // The version is reformatted in accordance with + // the https://semver.org specification. + return .{ + .commit = b.fmt("{s}-dev.{s}+{s}", .{ + tagged_ancestor, + commit_height, + commit_id[1..], + }), + }; + }, + else => std.debug.panic( + "Unexpected `git describe` output: {s}\n", + .{git_describe}, + ), + } } diff --git a/build.zig.zon b/build.zig.zon index d53a441..94cc625 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = "superhtml", - .version = "0.1.0", + .version = "0.4.0", .dependencies = .{ .@"zig-lsp-kit" = .{ .url = "git+https://github.com/kristoff-it/zig-lsp-kit#b4bf61d7fbf9cf7cfdb6f01b211947d2de3e42fd", @@ -14,6 +14,10 @@ .url = "git+https://github.com/kristoff-it/scripty#329e496043476254b6dd0990457cdbcee5c18191", .hash = "1220165019c862f801952d1529b990ec72de3d3b53c3a81be23500b1a10cac7266c2", }, + .@"zig-afl-kit" = .{ + .url = "git+https://github.com/kristoff-it/zig-afl-kit#4283624edfca1d3cc409bee4638be210c06ccecf", + .hash = "122098536c31acdeeee50e4648798b0295e4594cfb04b8d57d9434d5fc3b7dd8cca0", + }, }, .paths = .{ "LICENSE", diff --git a/src/cli.zig b/src/cli.zig index a7e4f78..1a528c3 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -6,8 +6,6 @@ const logging = @import("cli/logging.zig"); const fmt_exe = @import("cli/fmt.zig"); const lsp_exe = @import("cli/lsp.zig"); -pub const version = "0.1.4"; - pub const known_folders_config = .{ .xdg_force_default = true, .xdg_on_mac = true, @@ -91,7 +89,7 @@ fn oom() noreturn { } fn printVersion() noreturn { - std.debug.print("{s}\n", .{version}); + std.debug.print("{s}\n", .{build_options.version}); std.process.exit(0); } diff --git a/src/wasm.zig b/src/wasm.zig index 0067f83..7e52c68 100644 --- a/src/wasm.zig +++ b/src/wasm.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const super = @import("super"); const lsp_exe = @import("cli/lsp.zig"); -pub const version = "0.1.4"; - pub fn main() !void { const gpa = std.heap.wasm_allocator; try lsp_exe.run(gpa, &.{});