Repository: kassane/sokol-d
Pull request #67: `build.zig` cleanup
Author: login: kassane
Labels: name: cleanup
description: minimal refactoring
Created: 2025-04-28T18:27:24Z | Merged: 2025-05-07T21:58:14Z

## Steps
- [x] Fix debug mode for wasm32[^1]
- [x] Fix `ubsan` disable/enable
- [x] LLVM cpu-features mismatch fixed (generic isn't empty) see: https://github.com/ldc-developers/ldc/issues/4919
- [x] fix user_data example for wasm32-target (ignore `writeln`/`Exception`)

closes: #66 

[^1]: ldc2 no emit dwarf info for wasm32, see: https://github.com/ldc-developers/ldc/issues/4801


Diff:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 875f351c..b4e075a6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,9 @@
 name: D
 
-on: [push]
+on:
+  push:
+    branches: [ main ]
+  pull_request:
 
 jobs:
   build:
@@ -11,7 +14,7 @@ jobs:
     runs-on: ${{ matrix.runs-on }}
     steps:
       - uses: actions/checkout@v4
-      - uses: mlugg/setup-zig@v1
+      - uses: mlugg/setup-zig@v2
         # with:
         #   version: master
       - uses: dlang-community/setup-dlang@v2
@@ -56,4 +59,4 @@ jobs:
       - name: (Zig) Build Native
         run: zig build -Dimgui --summary all
       - name: (Zig + emsdk) Build Wasm
-        run: zig build -Dimgui -DzigCC --summary all -Dtarget=wasm32-emscripten-none -Doptimize=ReleaseSmall
+        run: zig build -Dimgui -DzigCC --summary all -Dtarget=wasm32-emscripten-none -Dcpu=generic
diff --git a/build.zig b/build.zig
index ac5fab57..6235ae34 100644
--- a/build.zig
+++ b/build.zig
@@ -22,6 +22,7 @@ pub const LibSokolOptions = struct {
     use_egl: bool = false,
     use_x11: bool = true,
     use_wayland: bool = false,
+    dynamic_linking: bool = false,
     emsdk: ?*Build.Dependency = null,
     use_ubsan: bool = false,
     use_tsan: bool = false,
@@ -76,19 +77,17 @@ fn rootPath() []const u8 {
 
 // build sokol into a static library
 pub fn buildLibSokol(b: *Build, options: LibSokolOptions) !*CompileStep {
-    const sharedlib = b.option(bool, "shared", "Build sokol dynamic library (default: static)") orelse false;
-    const lib = if (sharedlib) b.addSharedLibrary(.{
-        .name = "sokol",
-        .target = options.target,
-        .optimize = options.optimize,
-        .link_libc = true,
-    }) else b.addStaticLibrary(.{
+    const lib = b.addLibrary(.{
         .name = "sokol",
-        .target = options.target,
-        .optimize = options.optimize,
-        .link_libc = true,
+        .root_module = b.createModule(.{
+            .target = options.target,
+            .optimize = options.optimize,
+        }),
+        .linkage = if (options.dynamic_linking) .dynamic else .static,
     });
 
+    lib.linkLibC();
+
     lib.root_module.sanitize_c = options.use_ubsan;
     lib.root_module.sanitize_thread = options.use_tsan;
 
@@ -249,6 +248,7 @@ pub fn build(b: *Build) !void {
     const opt_with_sokol_imgui = b.option(bool, "imgui", "Add support for sokol_imgui.h bindings") orelse false;
     const opt_sokol_imgui_cprefix = b.option([]const u8, "sokol_imgui_cprefix", "Override Dear ImGui C bindings prefix for sokol_imgui.h (see SOKOL_IMGUI_CPREFIX)");
     const opt_cimgui_header_path = b.option([]const u8, "cimgui_header_path", "Override the Dear ImGui C bindings header name (default: cimgui.h)");
+    const sharedlib = b.option(bool, "shared", "Build sokol dynamic library (default: static)") orelse false;
     const sokol_backend: SokolBackend = if (opt_use_gl) .gl else if (opt_use_gles3) .gles3 else if (opt_use_wgpu) .wgpu else .auto;
     const imguiver_path = switch (b.option(
         imguiVersion,
@@ -281,6 +281,7 @@ pub fn build(b: *Build) !void {
         .use_wayland = opt_use_wayland,
         .use_x11 = opt_use_x11,
         .use_egl = opt_use_egl,
+        .dynamic_linking = sharedlib,
         .with_sokol_imgui = opt_with_sokol_imgui,
         .sokol_imgui_cprefix = opt_sokol_imgui_cprefix,
         .cimgui_header_path = opt_cimgui_header_path,
@@ -439,9 +440,29 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*Build.Step.InstallDir {
     if (options.betterC)
         ldc_exec.addArg("-betterC");
 
-    // verbose error messages
-    ldc_exec.addArg("-verrors=context");
+    // verbose messages
+    ldc_exec.addArgs(&.{
+        "-verrors=context",
+        "-vgc",
+        "-vtls",
+    });
 
+    if (options.optimize != .Debug) {
+        ldc_exec.addArgs(&.{
+            "--data-sections",
+            "--function-sections",
+        });
+        if (options.optimize != .ReleaseSafe) {
+            ldc_exec.addArgs(&.{
+                "-boundscheck=off",
+                "--enable-asserts=false",
+            });
+        } else {
+            ldc_exec.addArgs(&.{
+                "-boundscheck=safeonly",
+            });
+        }
+    }
     switch (options.optimize) {
         .Debug => {
             ldc_exec.addArgs(&.{
@@ -449,34 +470,22 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*Build.Step.InstallDir {
                 "-d-debug",
                 "--gc",
                 "-g",
-                "-gf",
-                "-gs",
-                "-vgc",
-                "-vtls",
+                "--write-experimental-debuginfo",
+                "--force-dwarf-frame-section",
                 "-boundscheck=on",
                 "--link-debuglib",
             });
         },
-        .ReleaseSafe => {
-            ldc_exec.addArgs(&.{
-                "-O",
-                "-boundscheck=safeonly",
-            });
-        },
-        .ReleaseFast => {
+        .ReleaseSafe, .ReleaseFast => {
             ldc_exec.addArgs(&.{
                 "-O",
-                "-boundscheck=off",
-                "--enable-asserts=false",
-                "--strip-debug",
             });
         },
         .ReleaseSmall => {
             ldc_exec.addArgs(&.{
-                "-Oz",
-                "-boundscheck=off",
-                "--enable-asserts=false",
+                "-Os",
                 "--strip-debug",
+                "--strip-global-constants",
             });
         },
     }
@@ -720,8 +729,32 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*Build.Step.InstallDir {
 
     ldc_exec.addArg(b.fmt("-mtriple={s}", .{mtriple}));
 
-    const cpu_model = options.target.result.cpu.model.llvm_name orelse "generic";
-    ldc_exec.addArg(b.fmt("-mcpu={s}", .{cpu_model}));
+    if (options.target.query.isNative()) {
+        ldc_exec.addArg("-mcpu=native");
+    } else {
+        const cpu_model = options.target.result.cpu.model.llvm_name orelse "generic";
+        ldc_exec.addArg(b.fmt("-mcpu={s}", .{cpu_model}));
+
+        // zig workaround for ldc2 get cpu-features
+        var cpu_args = std.ArrayList(u8).init(b.allocator);
+        defer cpu_args.deinit();
+        const all_features_list = options.target.result.cpu.arch.allFeaturesList();
+        for (all_features_list, 0..) |feature, index_usize| {
+            const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
+            const is_enabled = options.target.result.cpu.features.isEnabled(index);
+            if (feature.llvm_name) |llvm_name| {
+                const plus_or_minus = "-+"[@intFromBool(is_enabled)];
+                if (is_enabled) {
+                    try cpu_args.writer().print("{c}{s},", .{ plus_or_minus, llvm_name });
+                }
+            }
+        }
+        if (cpu_args.items.len > 0) {
+            // Remove trailing comma
+            _ = cpu_args.pop();
+            ldc_exec.addArg(b.fmt("-mattr={s}", .{cpu_args.items}));
+        }
+    }
 
     var outputDir: []const u8 = undefined;
     if (options.kind == .lib) {
@@ -960,6 +993,20 @@ const generated_zcc =
     \\             // NOT CHANGE!!
     \\         } else if (std.mem.endsWith(u8, arg, "as-needed")) {
     \\             // NOT CHANGE!!
+    \\         } else if (std.mem.startsWith(u8, arg, "/NOLOGO") or std.mem.startsWith(u8, arg, "/nologo")
+    \\             or std.mem.startsWith(u8, arg, "/P") or std.mem.startsWith(u8, arg, "/F")
+    \\             or std.mem.startsWith(u8, arg, "/Zc") or std.mem.startsWith(u8, arg, "/O") 
+    \\             or std.mem.startsWith(u8, arg, "/DE") or std.mem.startsWith(u8, arg, "/IG"))
+    \\         {
+    \\             // NOT CHANGE!!
+    \\         } else if (std.mem.endsWith(u8, arg, "32.lib")
+    \\             or std.mem.startsWith(u8, arg, "oldnames") 
+    \\             or std.mem.startsWith(u8, arg, "legacy_stdio_definitions")
+    \\             or std.mem.startsWith(u8, arg, "d3d11") 
+    \\             or std.mem.startsWith(u8, arg, "uuid") 
+    \\             or std.mem.startsWith(u8, arg, "winspool"))
+    \\         {
+    \\             // NOT CHANGE!!
     \\         } else if (std.mem.endsWith(u8, arg, "gcc") or
     \\             std.mem.endsWith(u8, arg, "gcc_s"))
     \\         {
@@ -1118,9 +1165,11 @@ pub fn emLinkStep(b: *Build, options: EmLinkOptions) !*Build.Step.InstallDir {
         const emcc_path = emSdkLazyPath(b, emsdk, &.{ "upstream", "emscripten", "emcc" }).getPath(b);
         const emcc = b.addSystemCommand(&.{emcc_path});
         emcc.setName("emcc"); // hide emcc path
+        if (b.verbose)
+            emcc.addArg("-v");
         if (options.optimize == .Debug) {
             emcc.addArgs(&.{
-                "-Og",
+                "-gsource-map",
                 "-sSAFE_HEAP=1",
                 "-sSTACK_OVERFLOW_CHECK=1",
             });
@@ -1312,7 +1361,7 @@ fn buildImgui(b: *Build, options: libImGuiOptions) !*CompileStep {
             },
             .flags = cflags.slice(),
         });
-        libimgui.root_module.sanitize_c = false;
+
         if (libimgui.rootModuleTarget().os.tag == .windows)
             libimgui.linkSystemLibrary("imm32");
 
diff --git a/build.zig.zon b/build.zig.zon
index e8c784e0..612334bb 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,11 +1,10 @@
 .{
     .name = .sokol,
-    .version = "0.2.0",
+    .version = "0.2.1",
     .min_zig_version = "0.14.0",
     .fingerprint = 0xf9f6f48d67869e98,
     .paths = .{
         "src",
-        "tools",
         "build.zig",
         "build.zig.zon",
         "LICENSE",
diff --git a/dub.sdl b/dub.sdl
index 87457367..d21773f2 100644
--- a/dub.sdl
+++ b/dub.sdl
@@ -1,5 +1,5 @@
 name "sokol-d"
-version "0.2.0"
+version "0.2.1"
 description "D bindings for sokol"
 authors "Matheus Catarino França" "Andre Weissflog"
 copyright "Copyright 2023-2024 - Matheus Catarino França"
@@ -24,7 +24,7 @@ buildType "release-nobounds" {
 buildType "release-debug" {
 	buildOptions "releaseMode" "debugInfo" "inline" "optimize"
 }
-toolchainRequirements dmd=">=2.105.0" ldc=">=1.35.0" gdc=">=9.3.0"
+toolchainRequirements frontend=">=2.105.0"
 
 configuration "default" {
 	targetType "sourceLibrary"
diff --git a/examples/user_data.d b/examples/user_data.d
index 5c90023a..87348045 100644
--- a/examples/user_data.d
+++ b/examples/user_data.d
@@ -43,17 +43,17 @@ void frame_userdata(scope void* userdata) @trusted
         {
             state.map.clear();
         }
-    }
-    debug
-    {
-        import std.stdio : writeln;
-
-        try
-        {
-            writeln(*state);
-        }
-        catch (Exception)
+        debug
         {
+            import std.stdio : writeln;
+
+            try
+            {
+                writeln(*state);
+            }
+            catch (Exception)
+            {
+            }
         }
     }
 


Reviews:
user: login: kassane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T19:26:16Z
user: login: kassane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T19:47:19Z
user: login: kassane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T20:02:42Z

Review comments:
diff_hunk: @@ -723,6 +728,30 @@ pub fn ldcBuildStep(b: *Build, options: DCompileStep) !*Build.Step.InstallDir {
     const cpu_model = options.target.result.cpu.model.llvm_name orelse "generic";
     ldc_exec.addArg(b.fmt("-mcpu={s}", .{cpu_model}));
 
+    // zig workaround for ldc2 get cpu-features
+    var cpu_args = std.ArrayList(u8).init(b.allocator);
+    defer cpu_args.deinit();
+    const all_features_list = options.target.result.cpu.arch.allFeaturesList();
+    for (all_features_list, 0..) |feature, index_usize| {
+        const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
+        const is_enabled = options.target.result.cpu.features.isEnabled(index);
+        if (feature.llvm_name) |llvm_name| {
+            const plus_or_minus = "-+"[@intFromBool(is_enabled)];
+            if (is_enabled) {
+                if (std.mem.endsWith(u8, llvm_name, "contextidrel2")) {
path: build.zig
user: login: kassane
body: Failure to capture flag correctly.
Unfortunately libsokol contains system-dependencies making it hard to cross-compile outside macOS.
created_at: 2025-05-03T19:26:16Z
author_association: COLLABORATOR
diff_hunk: @@ -960,6 +993,12 @@ const generated_zcc =
     \\             // NOT CHANGE!!
     \\         } else if (std.mem.endsWith(u8, arg, "as-needed")) {
     \\             // NOT CHANGE!!
+    \\         } else if (std.mem.startsWith(u8, arg, "/NOLOGO") or
+    \\             std.mem.startsWith(u8, arg, "/P") or std.mem.startsWith(u8, arg, "/F")
+    \\             or std.mem.startsWith(u8, arg, "/Zc") or std.mem.startsWith(u8, arg, "/O") 
+    \\             or std.mem.startsWith(u8, arg, "/DEB") or std.mem.startsWith(u8, arg, "/IG"))
+    \\         {
path: build.zig
user: login: kassane
body: Based: https://github.com/ldc-developers/ldc/blob/20d22b1e36242423e073b246a621c46217a92277/driver/linker-msvc.cpp#L120-L148
created_at: 2025-05-05T19:47:18Z
author_association: COLLABORATOR
diff_hunk: @@ -960,6 +993,12 @@ const generated_zcc =
     \\             // NOT CHANGE!!
     \\         } else if (std.mem.endsWith(u8, arg, "as-needed")) {
     \\             // NOT CHANGE!!
+    \\         } else if (std.mem.startsWith(u8, arg, "/NOLOGO") or
+    \\             std.mem.startsWith(u8, arg, "/P") or std.mem.startsWith(u8, arg, "/F")
+    \\             or std.mem.startsWith(u8, arg, "/Zc") or std.mem.startsWith(u8, arg, "/O") 
+    \\             or std.mem.startsWith(u8, arg, "/DEB") or std.mem.startsWith(u8, arg, "/IG"))
+    \\         {
path: build.zig
user: login: kassane
body: win32 libraries: https://github.com/ldc-developers/ldc/blob/20d22b1e36242423e073b246a621c46217a92277/driver/linker-msvc.cpp#L255-L263
created_at: 2025-05-05T20:02:42Z
author_association: COLLABORATOR

PR comments:
user: login: kassane
created_at: 2025-04-30T17:51:44Z
body: Curently, wasm32 build no emit warnings (ignored cpu-name or cpu-features).... :eyes:  CI log
author_association: COLLABORATOR
user: login: kassane
created_at: 2025-05-03T01:18:41Z
body: The last rebase dropped the use of `-mattr=` by manually assigning support to cpu-features.
I'm trying to assign following as an example the support for `-Xclang -target-feature` (attributed to `zig cc`/`translate-c` and D `importC` preprocessor)

https://github.com/ziglang/zig/blob/3b3c9d2081118c43311d1af726f575b93a6defdd/src/Compilation.zig#L5578-L5591
https://github.com/ldc-developers/ldc/blob/20d22b1e36242423e073b246a621c46217a92277/driver/cpreprocessor.cpp#L128-L143
author_association: COLLABORATOR
user: login: kassane
created_at: 2025-05-05T13:11:12Z
body: If enable v0.15.0-dev + ldc2 (currently LLVM19), mismatch cpu-features:
```bash
# LLVM20
'+bulk-memory-opt' is not a recognized feature for this target (ignoring feature)
'+call-indirect-overlong' is not a recognized feature for this target (ignoring feature)
``
author_association: COLLABORATOR