Im trying to compile a c-library using build.zig, the library is called htslib (used heavily in genomics). My goal is to have zig build the library, then be able to use the lib as a zig module, so eventually one could add htslib to a zig project using the package manager. I dont plan on adding any zig wrappers to functions, just to use them using cImport.
I managed to get a static lib built using build.zig, although building a shared library does not seem to work for some reason (I see lots of missing symbol errors during compile).
Next I created a zights.zig file which pulls in all the header files from htslib e.g. each line follows
pub const hts = @cImport({@cInclude("hts.h");});
pub const hfile = @cImport({@cInclude("hfile.h");});
Not sure if this is a good design? I was hoping the user could then access the functions using a separate program e.g.:
const h = @import("zights");
pub fn main() !void {
const f: h.hts.htsFile = h.sam.sam_open("blah.bam", "r");
_ = f;
}
Problem is the module doesn’t find the header files, and I see errors like ’ error: root struct of file ‘cimport’ has no member named …'. I can fix this by adding the full path to the zights.zig file though, indicating its probably something simple to fix.
Some questions I had:
- How do I add the include directory for the module, it can’t see the correct location at the moment?
- Where am I going wrong for building a shared library?
- Do I need to do anything with the libhts.a artefact - where should this live if its part of a zig module?
My build.zig file so far is:
const std = @import("std");
pub fn generateHeaders(b: *std.Build) !void {
//config.h
var genConfigFile = try std.fs.cwd().createFile("htslib/config.h", .{ .truncate = true });
defer genConfigFile.close();
const writer = genConfigFile.writer();
try writer.print(
\\ /* Default config.h generated by build.zig */
\\#define HAVE_LIBBZ2 1\n#define HAVE_LIBLZMA 1
\\#ifndef __APPLE__
\\#define HAVE_LZMA_H 1
\\#endif
\\#define HAVE_DRAND48 1
\\#define HAVE_LIBCURL 1
\\#define HAVE_POPCNT 1
\\#define HAVE_SSE4_1 1
\\#define HAVE_SSSE3 1
\\#define HAVE_AVX2 1
\\#define HAVE_AVX512 1
\\#define UBSAN 1
, .{});
//config_vars.h
var genVarsFile = try std.fs.cwd().createFile("htslib/config_vars.h", .{ .truncate = true });
const vars_writer = genVarsFile.writer();
const CPPFLAGS: []const u8 = b.graph.env_map.get("CPPFLAGS") orelse "";
const CFLAGS: []const u8 = b.graph.env_map.get("CFLAGS") orelse "";
const LDFLAGS: []const u8 = b.graph.env_map.get("LDFLAGS") orelse "";
const LIBS: []const u8 = b.graph.env_map.get("LIBS") orelse "";
try vars_writer.print(
\\#define HTS_CC "zig cc"
\\#define HTS_CPPFLAGS "{s}"
\\#define HTS_CFLAGS "{s} -g -Wall -O2 -fvisibility=hidden -fpic"
\\#define HTS_LDFLAGS "{s} -fvisibility=hidden "
\\#define HTS_LIBS "{s} -lz -lm -lbz2 -llzma -lcurl -lpthread"
, .{CPPFLAGS, CFLAGS, LDFLAGS, LIBS});
// version.h
const hts_version = "1.19.1";
var genVerFile = try std.fs.cwd().createFile("htslib/version.h", .{ .truncate = true });
const ver_writer = genVerFile.writer();
try ver_writer.print(
\\#define HTS_VERSION_TEXT "{s}"
, .{hts_version});
}
pub fn build(b: *std.Build) !void {
const LIBHTS_SOVERSION = 3;
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = std.builtin.OptimizeMode.ReleaseFast });
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
var cflags = std.ArrayList([]const u8).init(allocator);
defer cflags.deinit();
try cflags.append("-g");
try cflags.append("-Wall");
try cflags.append("-O2");
try cflags.append("-fvisibility=hidden");
try cflags.append("-fpic");
// try cflags.append("-D_XOPEN_SOURCE=600"); // Needed? Maybe 700?
// Platform specific build options
const target_os = target.query.os_tag;
try cflags.append("-dynamiclib");
if (target_os == .macos) {
std.debug.print("Building for macOS\n", .{});
try cflags.append("-dynamiclib");
} else if (target_os == .linux) {
std.debug.print("Building for Linux\n", .{});
try cflags.append("-shared");
try cflags.append("-Wl");
const soname = try std.fmt.allocPrint(allocator, "-Wl,-soname,libhts.so.{}", .{LIBHTS_SOVERSION});
defer allocator.free(soname);
try cflags.append(soname);
}
try generateHeaders(b);
const htslib = b.addStaticLibrary(.{
// const htslib = b.addSharedLibrary(.{. // <- this causes errors
.name = "hts",
.target = target,
.optimize = optimize,
});
//LIBHTS sources
const src_files = &[_][]const u8{
"htslib/kfunc.c",
"htslib/kstring.c",
"htslib/bcf_sr_sort.c",
"htslib/bgzf.c",
"htslib/errmod.c",
"htslib/faidx.c",
"htslib/header.c",
"htslib/hfile.c",
"htslib/hts.c",
"htslib/hts_expr.c",
"htslib/hts_os.c",
"htslib/md5.c",
"htslib/multipart.c",
"htslib/probaln.c",
"htslib/realn.c",
"htslib/regidx.c",
"htslib/region.c",
"htslib/sam.c",
"htslib/sam_mods.c",
"htslib/synced_bcf_reader.c",
"htslib/vcf_sweep.c",
"htslib/tbx.c",
"htslib/textutils.c",
"htslib/thread_pool.c",
"htslib/vcf.c",
"htslib/vcfutils.c",
"htslib/cram/cram_codecs.c",
"htslib/cram/cram_decode.c",
"htslib/cram/cram_encode.c",
"htslib/cram/cram_external.c",
"htslib/cram/cram_index.c",
"htslib/cram/cram_io.c",
"htslib/cram/cram_stats.c",
"htslib/cram/mFILE.c",
"htslib/cram/open_trace_file.c",
"htslib/cram/pooled_alloc.c",
"htslib/cram/string_alloc.c",
"htslib/hfile_libcurl.c",
};
for (src_files) |file| {
htslib.addCSourceFile(.{
.file = .{.path = file},
.flags = cflags.items,
});
}
htslib.addIncludePath(.{ .path = "htslib" });
htslib.addIncludePath(.{ .path = "htslib/cram" });
htslib.addIncludePath(.{ .path = "htslib/htscodecs/htscodecs" });
htslib.addIncludePath(.{ .path = "htslib/htslib" });
htslib.addIncludePath(.{ .path = "htslib/m4" });
htslib.addIncludePath(.{ .path = "htslib/os" });
htslib.linkLibC();
htslib.linkSystemLibrary("z");
htslib.linkSystemLibrary("m");
htslib.linkSystemLibrary("bz2");
htslib.linkSystemLibrary("lzma");
htslib.linkSystemLibrary("curl");
htslib.linkSystemLibrary("pthread");
b.default_step.dependOn(&htslib.step);
b.installArtifact(htslib);
const zights = b.addModule("zights", .{ .root_source_file = .{ .path = "src/zights.zig" } });
zights.addIncludePath(.{ .path = "htslib/htslib" });
zights.addIncludePath(.{ .path = "htslib/cram" });
zights.addLibraryPath(.{ .path = "zig-out/lib"});
// zights.linkSystemLibrary("hts"); // <- is something like this needed?
const zights_demo = b.addExecutable(.{
.name = "zights_demo",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
// these dont seem to do anything:
zights_demo.addIncludePath(.{ .path = "htslib/htslib" });
zights_demo.addIncludePath(.{ .path = "htslib/cram" });
zights_demo.addLibraryPath(.{ .path = "zig-out/lib"});
zights_demo.root_module.addAnonymousImport("zights", .{
.root_source_file = .{ .path = "src/zights.zig" },
});
b.installArtifact(zights_demo);
}
Thanks for the help, and any feedback would be much appreciated!