Use dependency that is a transitive dep of another one (harfbuzz, freetype)

Use dependency that is a transitive dep of another one (harfbuzz, freetype)
Hello, I’m trying to render text using harfbuzz and freetype. To do this, I shape the text using harfbuzz which gives me the glyphs I need to render, but to actually render I need to use freetype to get a bitmap. I’m using the allyourcodebase builds, but if I try to include both libraries and use them I get this error:

zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: error: file exists in modules 'freetype' and 'freetype0'
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: note: files must belong to only one module
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: note: file is the root of module 'freetype'
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: note: file is the root of module 'freetype0'

From what I unbderstand this is because harfbuzz has a dep on freetype, but I’m not sure what the problem is?
I tried accessing freetype through the harfbuzz import but it’s not pub. Does anyone have any suggestions/explanation?

build.zig:

const std = @import("std");

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

    const harfbuzz = b.dependency("harfbuzz", .{}).module("harfbuzz");
    const freetype = b.dependency("freetype", .{}).module("freetype");

    const exe = b.addExecutable(.{
        .name = "gigi",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "harfbuzz", .module = harfbuzz },
                .{ .name = "freetype", .module = freetype },
            },
        }),
    });

    b.installArtifact(exe);
    const run_exe = b.addRunArtifact(exe);

    const run_step = b.step("run", "Run gigi");
    run_step.dependOn(&run_exe.step);
}

build.zig.zon:

.{
    .name = .repro_freetype_harfbuzz,
    .version = "0.0.1",
    .dependencies = .{
        .harfbuzz = .{
            .url = "git+https://github.com/allyourcodebase/harfbuzz#d055840ecefefcd57f5ddeb2fa8c83fafed1a40b",
            .hash = "harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw",
        },
        .freetype = .{
            .url = "git+https://github.com/allyourcodebase/freetype#884fd2235e6ae1ec4306eda30b3259f20930ed2e",
            .hash = "freetype-2.14.3-C3-WdXSKAAB-kHs4qgRoeoFnRkNQn6M_SSlUGZ6ywgBQ",
        },
    },
    .minimum_zig_version = "0.16.0",
    .paths = .{""},
    .fingerprint = 0x7d8b6d4c3f9364cf,
}

src/main.zig:

const hb = @import("harfbuzz").c;
const ft = @import("freetype");

pub fn main() void {
    const text_buffer = hb.hb_buffer_create();
    defer hb.hb_buffer_destroy(text_buffer);

    const lib = try ft.Library.init();
    defer lib.deinit();
}

You can use freetype from the harfbuzz dep to get around the error.

const std = @import("std");

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

    const harfbuzz = b.dependency("harfbuzz", .{});
    const harfbuzz_mod = harfbuzz.module("harfbuzz");
    const freetype = harfbuzz.builder.dependency("freetype", .{}).module("freetype");

    const exe = b.addExecutable(.{
        .name = "gigi",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{ .name = "harfbuzz", .module = harfbuzz_mod },
                .{ .name = "freetype", .module = freetype },
            },
        }),
    });

    b.installArtifact(exe);
    const run_exe = b.addRunArtifact(exe);

    const run_step = b.step("run", "Run gigi");
    run_step.dependOn(&run_exe.step);
}

and remove freetype deps from your build.zig.zon

3 Likes

This results in the same error:

zig build
install
└─ install gigi
   └─ compile exe gigi Debug native 1 errors
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: error: file exists in modules 'freetype' and 'freetype0'
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: note: files must belong to only one module
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: note: file is the root of module 'freetype'
zig-pkg/harfbuzz-14.1.0-Ip1VAuGZAAC1jdxPHOMPoHyi-50Hm_q4bveaVflELHGw/freetype.zig:1:1: note: file is the root of module 'freetype0'

This error happens because the freetype module is instantiated twice, which is because it is created with different options, so it can’t be reused. You create it with no options (.{}), but harfbuzz creates it like this:

const freetype = b.dependency("freetype", .{
    .target = target,
    .optimize = optimize,
    .@"enable-libpng" = true,
});

If you create it with the same options, it seems to work. Note that you should also pass the target and optimize options to harfbuzz, otherwise there will be a difference when adding those options to the build command:

const harfbuzz = b.dependency("harfbuzz", .{
    .target = target,
    .optimize = optimize,
}).module("harfbuzz");
const freetype = b.dependency("freetype", .{
    .target = target,
    .optimize = optimize,
    .@"enable-libpng" = true,
}).module("freetype");

Though I wonder if there is a solution if you want to pass different options to freetype than harfbuzz does.

2 Likes

Nice, thanks for the trick!