Translation errors trying to import libtcod

Hi,

I’m trying to import libtcod using linkSystemLibrary() (I would have preferred to pull it in with zig fetch but I couldn’t figure out how to get it to find the header), and I’m getting compilaiton errors that look like the Zig compiler is having a hard time expanding a macro in the header files:

install
└─ install gonif
   └─ compile exe gonif Debug native 22 errors
src/root.zig:2:17: error: C import failed
const libtcod = @cImport(@cInclude("libtcod.h"));
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    print_tcod: src/root.zig:6:12
    main: src/main.zig:7:21
    4 reference(s) hidden; use '-freference-trace=6' to see all references
error: translation failure
/usr/include/libtcod/console_etc.h:111:1: error: unknown type name 'pragma'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/console_etc.h:111:1: error: expected ';', found 'an identifier'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/console_etc.h:111:1: error: unknown type name 'diagnostic'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/console_etc.h:111:1: error: expected ';', found 'a string literal'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/mouse.h:45:1: error: unknown type name 'pragma'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/mouse.h:45:1: error: expected ';', found 'an identifier'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/mouse.h:45:1: error: unknown type name 'diagnostic'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/mouse.h:45:1: error: expected ';', found 'a string literal'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/../sys.h:240:1: error: unknown type name 'pragma'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/../sys.h:240:1: error: expected ';', found 'an identifier'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/../sys.h:240:1: error: unknown type name 'diagnostic'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/../sys.h:240:1: error: expected ';', found 'a string literal'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/event.h:42:1: error: unknown type name 'pragma'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/event.h:42:1: error: expected ';', found 'an identifier'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/event.h:42:1: error: unknown type name 'diagnostic'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/sdl2/event.h:42:1: error: expected ';', found 'a string literal'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/txtfield.h:53:1: error: unknown type name 'pragma'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/txtfield.h:53:1: error: expected ';', found 'an identifier'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/txtfield.h:53:1: error: unknown type name 'diagnostic'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
/usr/include/libtcod/txtfield.h:53:1: error: expected ';', found 'a string literal'
TCODLIB_BEGIN_IGNORE_DEPRECATIONS
^
error: 22 compilation errors
failed command: /usr/bin/zig build-exe -ltcod -ODebug --dep gonif -Mroot=/home/alter_kaker/code/gonif/src/main.zig -Mgonif=/home/alter_kaker/code/gonif/src/root.zig -lc --cache-dir .zig-cache --global-cache-dir /home/alter_kaker/.cache/zig --name gonif --zig-lib-dir /usr/lib/zig/ --listen=-

Build Summary: 0/3 steps succeeded (1 failed)
install transitive failure
└─ install gonif transitive failure
   └─ compile exe gonif Debug native 22 errors

error: the following build command failed with exit code 1:

Please advise, this is my first time trying to compile with a C dependency.

My build.zig is the standard one from zig init with the following added:

exe.linkSystemLibrary("tcod");
exe.linkLibC();

Thanks!

PS. I have libtcod-2.2.1 installed on my system

Hi @alter_kaker
Welcome to ziggit :slight_smile:

The problem is the definition of TCODLIB_BEGIN_IGNORE_DEPRECATIONS, used to suppress warnings.
It is defined in src/libtcod/config.h as some kind of _Pragma statement for gnu c and msvc.

A workaround is to patch your config to always define them as:

#define TCODLIB_BEGIN_IGNORE_DEPRECATIONS
#define TCODLIB_END_IGNORE_DEPRECATIONS

i.e. as statements that do nothing.

Another possible workaround is to #undef __GNUC__ and #undef _MSC_VER before including libtcod.h. (This might have unintended side effects)
e.g.

const libtcod = @cImport({
    @cUndef("__GNUC__");
    @cUndef("_MSC_VER");
    @cInclude("libtcod.h");
});
2 Likes

Just tried this in a debian environment where libtcod is available in the package repos and starting from a zig init build it seems this works

main.zig:


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

...

pub fn main() !void {
    std.debug.print("{}.{}.{}\n", .{
        c.TCOD_MAJOR_VERSION,
        c.TCOD_MINOR_VERSION,
        c.TCOD_PATCHLEVEL,
    });
}

and in build.zig all you should need is


// Add libc to your module, something like
exe.root_module.link_libc = true;
// link the library
exe.root_module.linkSystemLibrary("tcod", .{});

Then zig run gives me

$ zig build run
1.24.0

Edit:

Ah didn’t see @dimdin’s reply before posting. But hm, I didn’t seem to need those work-arounds for printing the version macros at least :thinking:

1 Like

@barath thank you, but this seems to result in the same errors when I try to print a constant from the library. I wonder what’s the difference in our setup. I’m running endeavorOS and I got libtcod from the repositories.

@dimdin Thanks for the welcome! I tried the second workaround you suggested, and I’m getting brand new errors!

run
└─ run exe gonif
   └─ compile exe gonif Debug native 10 errors
src/root.zig:2:17: error: C import failed
const libtcod = @cImport({
                ^~~~~~~~
referenced by:
    print_tcod: src/root.zig:10:8
    main: src/main.zig:7:21
    4 reference(s) hidden; use '-freference-trace=6' to see all references
error: translation failure
/usr/include/bits/floatn-common.h:214:15: error: cannot combine with previous 'float' specifier
typedef float _Float32;
              ^
/usr/include/bits/floatn-common.h:214:23: error: expected identifier or '('
typedef float _Float32;
                      ^
/usr/include/bits/floatn-common.h:251:16: error: cannot combine with previous 'double' specifier
typedef double _Float64;
               ^
/usr/include/bits/floatn-common.h:251:24: error: expected identifier or '('
typedef double _Float64;
                       ^
/usr/include/bits/floatn-common.h:268:16: error: cannot combine with previous 'double' specifier
typedef double _Float32x;
               ^
/usr/include/bits/floatn-common.h:268:25: error: expected identifier or '('
typedef double _Float32x;
                        ^
/usr/include/bits/floatn-common.h:285:21: error: cannot combine with previous 'long double' specifier
typedef long double _Float64x;
                    ^
/usr/include/bits/floatn-common.h:285:30: error: expected identifier or '('
typedef long double _Float64x;
                             ^
error: 10 compilation errors
failed command: /usr/bin/zig build-exe -ltcod -ODebug --dep gonif -Mroot=/home/alter_kaker/code/gonif/src/main.zig -Mgonif=/home/alter_kaker/code/gonif/src/root.zig -lc++ -lc --cache-dir .zig-cache --global-cache-dir /home/alter_kaker/.cache/zig --name gonif --zig-lib-dir /usr/lib/zig/ --listen=-

Build Summary: 0/5 steps succeeded (1 failed)
run transitive failure
└─ run exe gonif transitive failure
   ├─ compile exe gonif Debug native 10 errors
   └─ install transitive failure
      └─ install gonif transitive failure
         └─ compile exe gonif Debug native (reused)

So, Progress. Thanks again :slight_smile:

Hm never used enadeavor :thinking:

Is this reproducible for you even with a minimal project like you get with zig init? ie do you add any other c include paths that might affect this? You can run zig build with --verbose-cc to double-check how the compiler ultimately gets invoked.

1 Like

Create a file called fix.h with the following contents:

#define _Pragma(x)

Use the following code to import libtcod:

const libtcod = @cImport({
    @cInclude("fix.h");
    @cInclude("libtcod.h");
});

And finally add the directory that fix.h is placed, in the list of directories that zig uses. (e.g. exe.addIncludePath(b.path("src/"));)

This normally fixes the issue.


The problem is that:

  • zig translate-c does not implement the new C _Pragma
    and that
  • libtcod assumes that _Pragma is avaliable if __GNUC__ is defined.

Both are bugs!

2 Likes

Thanks for the detailed instructions, I appreciate your time. Where can I find some documentation on how the include paths work? I’m having some issues with fix.h not being found (it’s insrc/, right next to the zig sources, and that’s what I added), and I assume I’m missing something. I’m not really used to having this much trouble figuring things out on my own :sweat_smile:

Once that is sorted out, would it make sense to include libtcod right in fix.h or maybe something suitably named, along with the macro definition, and import just that?

EndeavorOS is Arch based. It’s basically Arch minus having to mess with setting it up; I did that once and decided I don’t need to do it again :slight_smile:

I’m in a perfectly minimal project, there’s nothing in there that wasn’t discussed in this thread, except a call to std.fmt.debug() to print a constant from the library :grin:

1 Like

You need to tell zig that the module which needs the fix.h header file should add an include path to it to the compiler args. If your header file(s) are in <build>/src you can do something like

exe.root_module.addIncludePath(b.path("src"));

For figuring out things like that I feel setting up the zls language server for your IDE is invaluable, it let’s you explore the APIs of things like modules a lot easier with just going through auto completes.

There are also a bunch of zigified C/C++ projects to take inspiration from: GitHub - zigcc/awesome-zig: A collection of awesome projects in Zig.

So when adding src to include paths, that refers to the src directory in the project root? It should be obvious, but I’m trying to understand why the compiler isn’t finding my header file.

Thanks for the suggestions. I do have zls set up, with build on save enabled. I’ll review some of the project links in the repo. Maybe I’ll also switch to zig stable to see if things go a bit more smoothly.

In the end I did get it to build, by following advice in this comment on a bug ticket (not sure if it’s the same bug, since it’s for 0.15 and I’m running 0.16, but hey, it worked!).

build.zig:

    const libtcod = b.addTranslateC(.{
        .root_source_file = b.path("src/libtcod.h"),
        .target = target,
        .optimize = optimize,
        .link_libc = true,
    }).createModule();

    exe.linkSystemLibrary("tcod");
    exe.linkLibC();
    exe.root_module.addImport("libtcod", libtcod);

src/libtcod.h:

#define _Pragma(x)
#include <libtcod.h>

Builds and runs. Thanks everyone!!!