Translate-C error on 0.17.0-dev.607+456b2ec07 when importing GTK4 libraries

Hello all,

I recently started a new project to rewrite one of my existing C#/.NET/WPF projects into Zig + GTK 4 (project will be 2-BSD licensed so dynamically linking GTK-4 is a requirement since I don’t want it to become LGPL). I’ve written/released and currently maintain a few other Zig projects, but this will be my first GUI project in Zig and also the first one that I’m starting to link with external C libraries. I ran into the same issue that was described here since I first started the project on 0.16.0, after looking at the comments, and the status of the tickets in the upstream translate-c and arocc projects, it seemed like everything was already closed/resolved/merged, so I thought maybe I need to update to latest master for now and then I can chill on 0.17.0 once that gets officially released. However, even on 0.17.0-dev.607+456b2ec07, I get the same issue. So I wanted to confirm if it’s just me wiring things incorrectly, or if the changes aren’t in the latest master yet. I’m also fine downgrading to 0.16.0, and importing translate-c as an external dependency if that makes things any easier since I normally like to track the latest stable release, and upgrade to the next stable when I get a chance. But if that’s too difficult I’m fine with tracking master for this project and stabilizing on 0.17.0 for now. Thank you!

I’m running all of this on FreeBSD 15.0-RELEASE. pkgconf-2.4.3,1 and gtk4-4.20.3 are already installed on the host and Zig is detecting them correctly. Without pkgconf being installed, I was getting an issue about gtk/gtk.h not being found even though the linkSystemLibrary("gtk-4", .{}); call didn’t fail and found the correct library on FreeBSD. Once I switched to pkgconf, and changed it from “gtk-4” to “gtk4”, then it found the rest of the stuff and that’s when I started getting the multi pragma failure.

zig version

jon@leslie:~/Projects/App $ zig version
0.17.0-dev.607+456b2ec07

error when attempting to import gtk/gtk.h

jon@leslie:~/Projects/App $ zig build
install
└─ install App
   └─ compile exe App Debug native
      └─ translate-c 2 errors
error: translation failure
/usr/local/include/gtk-4.0/gdk/version/gdkversionmacros.h:19:2: error: \"Only <gdk/gdk.h> can be included directly.\"
#error "Only <gdk/gdk.h> can be included directly."
 ^
error: 2 compilation errors
failed command: /home/jon/.zig/zig translate-c -lc --cache-dir .zig-cache --global-cache-dir /home/jon/.cache/zig -I/usr/local/include/gtk-4.0 -I/usr/local/include/pango-1.0 -I/usr/local/include/fribidi -I/usr/local/include -I/usr/local/include/harfbuzz -I/usr/local/include/gdk-pixbuf-2.0 -I/usr/local/include/cairo -I/usr/local/include/freetype2 -I/usr/local/include/libpng16 -D_THREAD_SAFE -I/usr/local/include/pixman-1 -I/usr/local/include/graphene-1.0 -I/usr/local/lib/graphene-1.0/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -L/usr/local/lib -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lvulkan -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lintl /home/jon/Projects/App/src/c.h --listen=-

Build Summary: 0/4 steps succeeded (1 failed)
install transitive failure
└─ install App transitive failure
   └─ compile exe App Debug native transitive failure
      └─ translate-c 2 errors

error: the following maker command exited with code 1:
/home/jon/.cache/zig/o/201b413da3e9f6d025320505604c88e4/maker --zig /home/jon/.zig/zig --zig-lib-dir /home/jon/.zig/lib --build-root /home/jon/Projects/App --local-cache .zig-cache --global-cache /home/jon/.cache/zig --configuration .zig-cache/c/1efa3748f8b1e0f0168dbc146a0def18 --seed 0xfd57a27f

build.zig

...

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Translate GTK / C Dependencies
    const translate_c = b.addTranslateC(.{
        .root_source_file = b.path("src/c.h"),
        .target = target,
        .optimize = optimize,
    });
    translate_c.linkSystemLibrary("gtk4", .{});

    const mod = b.addModule("App", .{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
    });

    const exe = b.addExecutable(.{
        .name = "App",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "App", .module = mod },
                .{ .name = "c", .module = translate_c.createModule() },
            },
        }),
    });
 }

...

src/c.h

#include <gtk/gtk.h>

Well, it sure does look like the compile error wants your src/c.h file to be

#include <gdk/gdk.h>

obviously you didn’t need me to tell you that though, so let us know why you’re not doing that :slight_smile:

From what I can tell from the GTK basic tutorial, their documentation just mentions that we need to import gtk/gtk.h for it to work.

#include <gtk/gtk.h>

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
  gtk_window_present (GTK_WINDOW (window));
}

int
main (int    argc,
      char **argv)
{
  GtkApplication *app;
  int status;

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}

ahh. well, all i’m doing is reading what’s on the screen and offering a suggestion; you don’t have to actually try the suggestion :slight_smile:

Haha. I did try it but it lead to another issue which I feel may be going in the wrong direction given the GTK tutorial. If I do switch to gdk/gdk.h, it yields the following:

run
└─ run exe App
   └─ install
      └─ install App
         └─ compile exe App Debug native 1 errors
.zig-cache/o/51b847d0dde74af4b2dcfa5f8e890da7/c.zig:6320:194: error: expected ';' after statement
        if (__builtin.constant_p(!(@as(c_int, 0) != 0)) != 0) if (!(@as(c_int, 0) != 0)) _ = g_string_free(string, @intFromBool(!(@as(c_int, 0) != 0))) else _ = g_string_free_and_steal(string) else _ = g_string_free(string, @intFromBool(!(@as(c_int, 0) != 0)));
                                                                                                                                                                                                 ^~~~
error: 1 compilation errors
failed command: /home/jon/.zig/zig build-exe -ODebug --dep App --dep c -Mroot=/home/jon/Projects/App/src/main.zig -MApp=/home/jon/Projects/App/src/root.zig -I/usr/local/include/gtk-4.0 -I/usr/local/include/pango-1.0 -I/usr/local/include/fribidi -I/usr/local/include -I/usr/local/include/harfbuzz -I/usr/local/include/gdk-pixbuf-2.0 -I/usr/local/include/cairo -I/usr/local/include/freetype2 -I/usr/local/include/libpng16 -D_THREAD_SAFE -I/usr/local/include/pixman-1 -I/usr/local/include/graphene-1.0 -I/usr/local/lib/graphene-1.0/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -L/usr/local/lib -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lvulkan -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lintl -ODebug -Mc=.zig-cache/o/51b847d0dde74af4b2dcfa5f8e890da7/c.zig -lc --cache-dir .zig-cache --global-cache-dir /home/jon/.cache/zig --name App --zig-lib-dir /home/jon/.zig/lib/ --listen=-

Build Summary: 1/6 steps succeeded (1 failed)
run transitive failure
└─ run exe App transitive failure
   ├─ compile exe App Debug native 1 errors
   └─ install transitive failure
      └─ install App transitive failure
         └─ compile exe App Debug native (+1 more reused dependencies)

error: the following maker command exited with code 1:
/home/jon/.cache/zig/o/201b413da3e9f6d025320505604c88e4/maker --zig /home/jon/.zig/zig --zig-lib-dir /home/jon/.zig/lib --build-root /home/jon/Projects/App --local-cache .zig-cache --global-cache /home/jon/.cache/zig --configuration .zig-cache/c/1efa3748f8b1e0f0168dbc146a0def18 --seed 0xf3dc5e45 run
jon@leslie:~/Projects/App $

Currently the only way to correctly include gtk.h is to use version 0.15.x with @cInclude.
Using 0.16.0, both @cInclude and translate-c module fail to translate gtk.h.

0.17.0-dev with translate-c module have the latest fixes for the gtk.h translation problems, but it also fails because the arocc —used in translate-c— is not yet synchronized with the latest zig changes (metadata for struct declarations).because the changes in arocc are ahead of the current zig master.

The translate-c as a module is a great idea because it can have fixes after 0.17.0 is released.

So currently what can we do:

  • Use 0.15.1
  • Wait for 0.17.0
  • Help fix translate-c/arocc
  • Donate some money to zig software foundation, so others can fix the problems.
    :slight_smile:
4 Likes

Thanks for the info @dimdin. I just wanted to make sure the issue wasn’t me before I go on another rabbit hole. I may downgrade from master to 0.15.1, but I’ll see how far I can get since a good chunk of my logic isn’t UI code. And yes, I’ve donated to ZSF multiple times throughout the years. Another donation will be coming soon :).

1 Like

Just wanted to provide a small update. I was able to successfully get GTK 4 working with Zig 0.15.2 and get some self written bindings to work and render on the screen (I know there are existing Zig <> GTK libraries, but I’m also taking this as a learning experience for Zig<>C interop, and I don’t really need all of GTK’s features). However, after playing with GTK, as someone that’s porting an app I wrote about 8 years ago in C#/WPF/XAML (and I <3 C#/WPF/XAML btw), and previously after that having re-written it from Java/Swing (IIRC), I have to say GTK is pretty insane (not in a good way). I’m going to be starting again and going to give one of the newer native Zig UI toolkits a try, and learn about this “Immediate UI” type of architecture, which seems interesting to me. I think I’ll have a better experience overall. I think I’ll give @david_vanderson’s DVUI a shot. Since the UI will most likely now be in DVUI, this means my project can start in 0.16.0 again since DVUI is 0.16.0 compatible :).

For now, I’ll paste my existing GTK<>Zig code I wrote in case anyone needs it or wants to use it as an idea for brainstorming their own stuff.

const std = @import("std");
// const Io = std.Io;
const assert = std.debug.assert;

// const App = @import("App");

const c = @cImport({
    @cInclude("gtk/gtk.h");
});

pub const std_options: std.Options = .{
    .log_level = .debug,
};

fn print_hello(widget: *c.GtkWidget, data: c.gpointer) callconv(.c) void {
    _ = widget;
    _ = data;
    c.g_print("Hello World\n");
}

/// Create application UI and wire signals.
fn activate(app: *c.GtkApplication, _: c.gpointer) callconv(.c) void {
    // Create core application window object.
    const gtk_window_widget: *c.GtkWidget = Binder.GtkApplicationWindow(app);
    const window: *c.GtkWindow = Binder.AsWindow(gtk_window_widget);

    // Window Box
    const windowBox = Binder.Box.New(c.GTK_ORIENTATION_VERTICAL, 10);
    c.gtk_window_set_child(window, windowBox);

    const platformsBox = Binder.Box.New(c.GTK_ORIENTATION_VERTICAL, 10);
    c.gtk_widget_set_vexpand(platformsBox, 1);
    Binder.Box.Append(windowBox, platformsBox);

    // Buttons Box
    const buttonsBox = Binder.Box.New(c.GTK_ORIENTATION_HORIZONTAL, 10);
    Binder.Box.Append(windowBox, buttonsBox);

    // Buttons
    const oneButton = Binder.Button.New("Button 1", &print_hello);
    const twoButton = Binder.Button.New("Button 2", &print_hello);

    // Add buttons to buttons box.
    Binder.Box.Append(buttonsBox, oneButton);
    Binder.Box.Append(buttonsBox, twoButton);

    // Set remaining window properties.
    c.gtk_window_set_title(window, "App");
    c.gtk_window_set_default_size(window, 1150, 650);
    c.gtk_window_set_resizable(window, 0);
    c.gtk_window_present(window);
}

const Binder = struct {
    // TODO: See if we can consolidate the internal logic between normal and swapped.
    pub fn connect(widget: *c.GtkWidget, message: [*]const u8, callback: c.GCallback) void {
        const result = c.g_signal_connect_data(
            widget,
            message,
            callback,
            null,
            null,
            0,
        );

        // The handler ID (always greater than 0).
        // https://docs.gtk.org/gobject/func.signal_connect_data.html
        assert(result > 0);
    }

    // Not Currently Used
    pub fn connectSwapped(widget: *c.GtkWidget, message: [*]const u8, callback: c.GCallback, window: *c.GtkWindow) void {
        const result = c.g_signal_connect_data(
            widget,
            message,
            callback,
            window,
            null,
            c.G_CONNECT_SWAPPED,
        );

        // The handler ID (always greater than 0).
        // https://docs.gtk.org/gobject/func.signal_connect_data.html
        assert(result > 0);
    }

    pub fn GtkApplicationWindow(app: *c.GtkApplication) *c.GtkWidget {
        return c.gtk_application_window_new(app);
    }

    pub fn Callback(func: *const fn (*c.GtkWidget, c.gpointer) callconv(.c) void) c.GCallback {
        return @ptrCast(@alignCast(func));
    }

    // Not Currently Used
    pub fn WindowDestroyCallback() c.GCallback {
        return @ptrCast(@alignCast(&c.gtk_window_destroy));
    }

    pub fn AsWindow(widget: *c.GtkWidget) *c.GtkWindow {
        return @as(*c.GtkWindow, @ptrCast(@alignCast(widget)));
    }

    pub fn AsBox(widget: *c.GtkWidget) *c.GtkBox {
        return @as(*c.GtkBox, @ptrCast(@alignCast(widget)));
    }

    // ----------------- Higher Functions -----------------

    pub const Button = struct {
        // Creates a button and optionally connects a signal to it.
        //
        // Caller owns memory.
        pub fn New(label: [*]const u8, func: ?*const fn (*c.GtkWidget, c.gpointer) callconv(.c) void) *c.GtkWidget {
            const button: *c.GtkWidget = c.gtk_button_new_with_label(label);

            if (func) |signal| {
                Binder.connect(button, "clicked", Binder.Callback(signal));
            }

            return button;
        }
    };

    pub const Box = struct {
        // c_int = gtk orientation enum
        pub fn New(orientation: c.GtkOrientation, spacing: u8) *c.GtkWidget {
            const box: *c.GtkWidget = c.gtk_box_new(orientation, spacing);
            c.gtk_widget_set_halign(box, c.GTK_ALIGN_CENTER);
            c.gtk_widget_set_valign(box, c.GTK_ALIGN_CENTER);
            return box;
        }

        pub fn Append(box: *c.GtkWidget, widget: *c.GtkWidget) void {
            c.gtk_box_append(Binder.AsBox(box), widget);
        }
    };
};

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    const allocator = gpa.allocator();
    var arena_alloc: std.heap.ArenaAllocator = .init(allocator);
    const arena = arena_alloc.allocator();
    _ = arena;

    std.log.debug("Starting App ...", .{});
    // try App.run(arena);

    //defer allocator.deinit();

    //var arena: std.heap.ArenaAllocator(allocator);
    //const arena: std.mem.Allocator = init.arena.allocator();
    //init: std.process.Init
    // const io = init.io;

    // var stdout_buffer: [1024]u8 = undefined;
    // var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
    // const stdout_writer = &stdout_file_writer.interface;
    // try stdout_writer.flush(); // Don't forget to flush!

    // Accessing command line arguments:
    // const args = try init.minimal.args.toSlice(arena);
    // for (args) |arg| {
    //     std.log.info("arg: {s}", .{arg});
    // }

    const app = c.gtk_application_new(
        "org.test.app",
        c.G_APPLICATION_DEFAULT_FLAGS,
    );

    _ = c.g_signal_connect_data(
        app,
        "activate",
        @as(c.GCallback, @ptrCast(@alignCast(&activate))),
        null,
        null,
        0,
    );

    _ = c.g_application_run(
        @as(*c.GApplication, @ptrCast(@alignCast(app))),
        0,
        null,
    );

    c.g_object_unref(app);
}

That sounds very similar to my story as well!

A good place to start is watching the zig showtime episode: Zig SHOWTIME

It’s old enough that the code samples aren’t exact, but it’s a good introduction to immediate-mode vs. retained-mode.

1 Like

Thanks @david_vanderson. I’ll take a look and will probably reach out to you in the future privately. I’m currently in the early stages of fully rewriting my Diablo II version switcher / mod manager / character isolator, Cactus, into Zig from scratch, fully built for only FreeBSD/Linux, no Windows support anymore, and fully integrating Wine into it to launch Diablo II (original, not Resurrected). So for the MVP all of the current functionality, safety checks, and UI design is the goal. I think the most complicated component in this app is the list view.

2 Likes

Wanted to post a reply here because I was able to compile with GTK4 on the current master branch of Zig.

To do it, I added translate-c as a dependency, passed an option to link it to GTK4, and then added the module to the exe.

Run this in your build directory:

zig fetch --save git+https://codeberg.org/ziglang/translate-c

As of writing this post, the current commit hash was: ba4288f6c0e8054d7894266949d4fa08bfc0212a

This is my build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {

    // Allows user to pick target
    const target = b.standardTargetOptions(.{});
    // Allows user to pick optimization mode
    const optimize = b.standardOptimizeOption(.{});

    // add translate-c as a dep
    const translate_c = b.dependency("translate_c", .{});

    // Import the Translator wrapper
    const Translator = @import("translate_c").Translator;

    // c.h contains #include <gtk/gtk.h>
    // we link gtk4 by passing it as an option here
    const t: Translator = .init(translate_c, .{ .c_source_file = b.path("src/c.h"), .target = target, .optimize = optimize, .link_system_libs = &.{
        .{ .name = "gtk4" },
    } });

    // builds the actual exe
    const exe = b.addExecutable(.{
        .name = "gtk_app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),

            .target = target,
            .optimize = optimize,
            .link_libc = true,
            .imports = &.{.{
                .name = "c",
                .module = t.mod,
            }},
        }),
    });
    b.installArtifact(exe);
}

Then you can import it in your main.zig like so:
const c = @import("c");

This doesn’t work with Zig 16.0, you have to use the master branch. I compiled with zig-x86_64-linux-0.17.0-dev.1275+59a628c6d

Give it a shot, @fearedbliss

4 Likes

Thanks @ccc, although I already finished writing the whole project using DVUI for the UI layer instead so I won’t be using GTK anymore.

Yes, it is great that arocc and translate-c evolved to handle gtk.h

Unfortunately zig pkgconfig is still broken. On debian translate-c cannot find gtk.h because the zig code cannot handle arbitary pkg-config cflags:

warning: pkg-config package gtk4 unknown flag: -mfpmath=sse
warning: pkg-config package gtk4 unknown flag: -msse
warning: pkg-config package gtk4 unknown flag: -msse2
warning: skipping pkg-config for package gtk4 due to unknown flags
./.zig-cache/o/40e134bea1adcfed61b64a55a615a22e/c.h:1:10: fatal error: 'gtk/gtk.h' not found
#include <gtk/gtk.h>
         ^
error: process exited with code 1

@dimdin I actually forgot to mention the fix for this because I ran into the same issue myself.

As we can tell, since the compiler doesn’t know how to deal with these flags, it rejects the output of pkg-config for gtk4. I’m not an expert on how pkg-config works but it retrieves the flags and includes needed to compile via .pc files.

If you look at the config for gtk4, you’ll see a dependency on a library called graphene (unrelated to GrapheneOS) and that includes the options -mfpmath=see, -msse, and msse2. So if you go into the file and just delete it from there, You’ll be able to compile it successfully. A quick google search will tell you these flags are related to SIMD.

On my machine, the files were in /usr/lib64/pkgconfig. This is definitely a hacky way and if there isn’t an issue opened already, it should be brought to the developers attention.

Hey folks, those with issues that want to use translate-c on 0.16.0 might want to check out the backport fork we’ve been maintaining for Ghostty:

This backport does translate GTK (let me know if you have issues though!)

WRT the pkg-config argument issues, I understand that a fix is likely coming soon for upstream. My backport does have a workaround, but it swaps out --cflags for --cflags-only-I, and is likely only a temporary fix until something more permanent is done.

This is not acceptable, because is is ignoring other important flags (e.g. -pthread)
I believe, the correct solution is to handle the -m cpu features.
Currently I comment out the last return statement in translate-c runPkgConfig; and this is also an unacceptable solution.

I’ve gone ahead and submitted https://codeberg.org/ziglang/translate-c/pulls/434 as Veikka was mentioning that as the possible solution for the time being. This is in the backport as well.