Issues Building Switch Homebrew with devkitPro and Zig 0.16

I’m trying to use devkitPro to create homebrew for the Nintendo Switch with Zig, but after upgrading from Zig 0.15.1 to Zig 0.16 I get a lot of type‑redefinition and function‑redefinition errors in libnx headers

this is the code I want to compile:

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

export fn main(_: c_int, _: [*]const [*:0]const u8) void {
    _ = c.consoleInit(null);
    defer c.consoleExit(null);

    _ = c.printf("Hello, Zig");
    while (c.appletMainLoop()) {
        c.consoleUpdate(null);
     } 
}

using:

zig build-obj --name zig-switch src/main.zig \
    -target aarch64-freestanding -mcpu=cortex_a57 \
    -I $DEVKITPRO/libnx/include/ \
    --libc libc.txt -lc

works fine on zig 0.15.1 but on zig 0.16 I get this errors (I cant upload the full logs):

src/main.zig:1:11: error: C import failed
const c = @cImport({
          ^~~~~~~~
referenced by:
    main: src/main.zig:7:9
    main: src/main.zig:6:1
    4 reference(s) hidden; use '-freference-trace=6' to see all references
error: translation failure
/opt/zig-bin-0.16.0/lib/include/arm_acle.h:21:2: error: \"ACLE intrinsics support not enabled.\"
#error "ACLE intrinsics support not enabled."
 ^
/opt/devkitpro/libnx/include/switch/arm/../types.h:48:35: error: typedef redefinition with different types ('struct (anonymous struct at /opt/devkitpro/libnx/include/switch/arm/../types.h:48:9)' vs 'struct (anonymous struct at /opt/devkitpro/libnx/include/switch/types.h:48:9)')
typedef struct { u8 uuid[0x10]; } Uuid;   ///< Unique identifier.

..................................
........................................
........................................

/opt/devkitpro/libnx/include/switch/crypto/../types.h:48:35: error: typedef redefinition with different types ('struct (anonymous struct at /opt/devkitpro/libnx/include/switch/crypto/../types.h:48:9)' vs 'struct (anonymous struct at /opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/../types.h:48:9)')
typedef struct { u8 uuid[0x10]; } Uuid;   ///< Unique identifier.
                                  ^
/opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/../types.h:48:35: note: previous definition is here
typedef struct { u8 uuid[0x10]; } Uuid;   ///< Unique identifier.
                                  ^
/opt/devkitpro/libnx/include/switch/crypto/../types.h:50:36: error: typedef redefinition with different types ('struct (anonymous struct at /opt/devkitpro/libnx/include/switch/crypto/../types.h:50:9)' vs 'struct (anonymous struct at /opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/../types.h:50:9)')
typedef struct { float value[3]; } UtilFloat3;   ///< 3 floats.
                                   ^
/opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/../types.h:50:36: note: previous definition is here
typedef struct { float value[3]; } UtilFloat3;   ///< 3 floats.
                                   ^
/opt/devkitpro/libnx/include/switch/crypto/sha256.h:25:3: error: typedef redefinition with different types ('struct (anonymous struct at /opt/devkitpro/libnx/include/switch/crypto/sha256.h:19:9)' vs 'struct (anonymous struct at /opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/sha256.h:19:9)')
} Sha256Context;
  ^
/opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/sha256.h:25:3: note: previous definition is here
} Sha256Context;
  ^
/opt/devkitpro/libnx/include/switch/crypto/sha256.h:28:6: error: redefinition of 'sha256ContextCreate' with a different type
void sha256ContextCreate(Sha256Context *out);
     ^
/opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/sha256.h:28:6: note: previous definition is here
void sha256ContextCreate(Sha256Context *out);
     ^
/opt/devkitpro/libnx/include/switch/crypto/sha256.h:30:6: error: redefinition of 'sha256ContextUpdate' with a different type
void sha256ContextUpdate(Sha256Context *ctx, const void *src, size_t size);
     ^
/opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/sha256.h:30:6: note: previous definition is here
void sha256ContextUpdate(Sha256Context *ctx, const void *src, size_t size);
     ^
/opt/devkitpro/libnx/include/switch/crypto/sha256.h:32:6: error: redefinition of 'sha256ContextGetHash' with a different type
void sha256ContextGetHash(Sha256Context *ctx, void *dst);
     ^
/opt/devkitpro/libnx/include/switch/runtime/devices/../../services/../crypto/sha256.h:32:6: note: previous definition is here
void sha256ContextGetHash(Sha256Context *ctx, void *dst);
     ^
/opt/zig-bin-0.16.0/lib/include/arm_acle.h:60:7: error: assignment to 'uint32_t' (aka 'unsigned int') from incompatible type 'void'
    v = __builtin_arm_ldrex(__p);

contents of the libc.txt used:

# The directory that contains `stdlib.h`.
# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`
include_dir=/opt/devkitpro/devkitA64/aarch64-none-elf/include

# The system-specific include directory. May be the same as `include_dir`.
# On Windows it's the directory that includes `vcruntime.h`.
# On POSIX it's the directory that includes `sys/errno.h`.
sys_include_dir=/opt/devkitpro/devkitA64/aarch64-none-elf/include

# The directory that contains `crt1.o` or `crt2.o`.
# On POSIX, can be found with `cc -print-file-name=crt1.o`.
# Not needed when targeting MacOS.
crt_dir=/opt/devkitpro/devkitA64/lib

# The directory that contains `vcruntime.lib`.
# Only needed when targeting MSVC on Windows.
msvc_lib_dir=

# The directory that contains `kernel32.lib`.
# Only needed when targeting MSVC on Windows.
kernel32_lib_dir=

# The directory that contains `crtbeginS.o` and `crtendS.o`
# Only needed when targeting Haiku.
gcc_dir=

I also tried to use zig translate-c with:

zig translate-c -target aarch64-freestanding \
    -I /opt/devkitpro/libnx/include \
    --libc libc.txt -lc /opt/devkitpro/libnx/include/switch.h > libnx.zig

but I get the exact same results as before.

The only thing that currently builds is manually declaring the needed libnx functions as extern symbols:

pub extern fn consoleInit(console: ?*void) void;
pub extern fn consoleExit(console: ?*void) void;
pub extern fn consoleUpdate(console: ?*void) void;
pub extern fn appletMainLoop() bool;
pub extern fn printf(fmt: [*:0]const u8, ...) c_int;

export fn main(_: c_int, _: [*]const [*:0]const u8) void {
    _ = consoleInit(null);
    defer consoleExit(null);

    _ = printf("Hello, Zig");
    while (appletMainLoop()) {
        consoleUpdate(null);
     } 
}

this works but is a pain in the ass.

If anyone has a proper fix or a set of flags that lets Zig compile against devkitPro/libnx without resorting to manual extern declarations, please share. This seems to be a fairly niche setup, so any insight would be greatly appreciated. Thanks!

You dropped the @cImport, however the real issue is most likely that you added the same cImport in multiple files, this creates separate types, instead have it in a single file as a pub declaration and then import that from other files.

I made a mistake when pasting the code in the post. The correct snippet is:

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

export fn main(_: c_int, _: [*]const [*:0]const u8) void {
    _ = c.consoleInit(null);
    defer c.consoleExit(null);

    _ = c.printf("Hello, Zig");
    while (c.appletMainLoop()) {
        c.consoleUpdate(null);
     } 
}

The whole code is in a single file, src/main.zig, it is not split across multiple files.
I have already edited the post with the correct code. Thanks for your response I appreciate the help.

Is there some kind of define that needs to be set so that only one of the implementations is active?

No, there is no define that needs to be set to activate only one implementation.
Im not sure if its useful, but a similar example in C can be found here: switch-examples/graphics/printing/hello-world/source/main.c at master · switchbrew/switch-examples · GitHub

Zig 0.16 swapped out the C compiler used by translate-c from LLVM to Aro, so you are probably running into this bug: #pragma once not respected when the same file is included twice via a non-canonical path - #404

The bug has already been fixed for 0.17.0-dev (master), but requires using the official translate-c package since the translate-c functionality has been removed from the compiler itself.

If you’re stuck on 0.16 you’re unfortunately out of luck for the time being, however it looks like 0.17 will get tagged and released shortly, most likely this month.

1 Like