diff --git a/.github/workflows/zig_build.yml b/.github/workflows/zig_build.yml new file mode 100644 index 00000000..32c908f2 --- /dev/null +++ b/.github/workflows/zig_build.yml @@ -0,0 +1,22 @@ +name: Zig Build + +on: [push, pull_request] + +jobs: + build: + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - uses: goto-bus-stop/setup-zig@v2 + with: + version: master + + - name: Build Summary + run: zig build -DBUILD_TESTS -DBUILD_EXAMPLES -DUSE_SYSTEM_MINIZIP --summary all -freference-trace \ No newline at end of file diff --git a/build.zig b/build.zig index 0b974a8f..858fd5da 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Path = std.Build.LazyPath; // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be libcuted by an external @@ -15,7 +16,13 @@ pub fn build(b: *std.Build) void { // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); - const shared = b.option(bool, "Shared", "Build the Shared Library [default: false]") orelse false; + const shared = b.option(bool, "SHARED_LIBRARY", "Build the Shared Library [default: false]") orelse false; + const examples = b.option(bool, "BUILD_EXAMPLES", "Build libxlsxwriter examples [default: false]") orelse false; + const tests = b.option(bool, "BUILD_TESTS", "Build libxlsxwriter tests [default: false]") orelse false; + const dtoa = b.option(bool, "USE_DTOA_LIBRARY", "Use the locale independent third party Milo Yip DTOA library [default: off]") orelse false; + const minizip = b.option(bool, "USE_SYSTEM_MINIZIP", "Use system minizip installation [default: off]") orelse false; + const md5 = b.option(bool, "USE_OPENSSL_MD5", "Build libxlsxwriter with the OpenSSL MD5 lib [default: off]") orelse false; + const stdtmpfile = b.option(bool, "USE_STANDARD_TMPFILE", "Use the C standard library's tmpfile() [default: off]") orelse false; const lib = if (shared) b.addSharedLibrary(.{ .name = "xlsxwriter", @@ -31,6 +38,13 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + lib.pie = true; + switch (optimize) { + .Debug, .ReleaseSafe => lib.bundle_compiler_rt = true, + else => lib.strip = true, + } + if (tests) + lib.defineCMacro("TESTING", null); lib.addCSourceFiles(&.{ "src/vml.c", "src/chartsheet.c", @@ -54,13 +68,255 @@ pub fn build(b: *std.Build) void { "src/table.c", "src/workbook.c", "src/packager.c", - }, &.{ - "-Wall", - "-Wextra", + }, cflags); + + // minizip + if (minizip) { + lib.addCSourceFiles(switch (target.getOsTag()) { + .windows => minizip_src ++ [_][]const u8{ + "third_party/minizip/iowin32.c", + }, + else => minizip_src, + }, cflags); + } + + // zig-pkg: download & build zlib (to all targets) + const zlib_dep = b.dependency("zlib", .{ + .optimize = optimize, + .target = target, }); - lib.addIncludePath("include"); - lib.addIncludePath("third_party"); + const zlib = zlib_dep.artifact("z"); + lib.linkLibrary(zlib); + lib.installLibraryHeaders(zlib); + + // md5 + if (!md5) + lib.addCSourceFile(.{ .file = .{ .path = "third_party/md5/md5.c" }, .flags = cflags }) + else + lib.linkSystemLibrary("crypto"); + + // dtoa + if (dtoa) + lib.addCSourceFile(.{ .file = Path.relative("third_party/dtoa/emyg_dtoa.c"), .flags = cflags }); + + // tmpfileplus + if (stdtmpfile) + lib.addCSourceFile(.{ .file = .{ .path = "third_party/tmpfileplus/tmpfileplus.c" }, .flags = cflags }) + else + lib.defineCMacro("USE_STANDARD_TMPFILE", null); + + lib.addIncludePath(Path.relative("include")); + lib.addIncludePath(Path.relative("third_party")); lib.linkLibC(); + + // get headers on include to zig-out/include lib.installHeadersDirectory("include", ""); + + // get binaries on zig-cache to zig-out b.installArtifact(lib); + + // build examples + if (examples) { + buildExe(b, .{ + .lib = lib, + .path = "examples/anatomy.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/array_formula.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/autofilter.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/background.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/chart_area.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/chart_column.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/data_validate.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/hello.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/watermark.c", + }); + buildExe(b, .{ + .lib = lib, + .path = "examples/worksheet_protection.c", + }); + } + // build tests + if (tests) { + buildTest(b, .{ + .lib = lib, + .path = "test/unit/app/test_app.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/chart/test_chart.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/chartsheet/test_chartsheet.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/content_types/test_content_types.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/content_types/test_content_types_write_default.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/content_types/test_content_types_write_override.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/relationships/test_relationships.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/app/test_app_xml_declaration.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/relationships/test_relationships_xml_declaration.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/custom/test_custom_xml_declaration.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/metadata/test_metadata_xml_declaration.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/core/test_core_xml_declaration.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/sst/test_shared_strings.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/workbook/test_workbook.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/xmlwriter/test_xmlwriter.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/table/test_table01.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/table/test_table02.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/table/test_table03.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/table/test_table04.c", + }); + buildTest(b, .{ + .lib = lib, + .path = "test/unit/styles/test_styles_write_border.c", + }); + } } + +fn buildExe(b: *std.Build, info: BuildInfo) void { + const exe = b.addExecutable(.{ + .name = info.filename(), + .optimize = info.lib.optimize, + .target = info.lib.target, + }); + exe.addCSourceFile(.{ .file = Path.relative(info.path), .flags = cflags }); + exe.linkLibrary(info.lib); + for (info.lib.include_dirs.items) |include| { + exe.include_dirs.append(include) catch {}; + } + exe.linkLibC(); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step( + b.fmt("{s}", .{info.filename()}), + b.fmt("Run the {s} test", .{info.filename()}), + ); + run_step.dependOn(&run_cmd.step); +} + +fn buildTest(b: *std.Build, info: BuildInfo) void { + const exe = b.addExecutable(.{ + .name = info.filename(), + .optimize = info.lib.optimize, + .target = info.lib.target, + }); + exe.defineCMacro("TESTING", null); + exe.addCSourceFile(.{ .file = Path.relative(info.path), .flags = cflags }); + exe.addCSourceFile(.{ .file = Path.relative("test/unit/test_all.c"), .flags = cflags }); + exe.addIncludePath(Path.relative("test/unit")); + exe.linkLibrary(info.lib); + exe.installLibraryHeaders(info.lib); + exe.linkLibC(); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step( + b.fmt("{s}", .{info.filename()}), + b.fmt("Run the {s} test", .{info.filename()}), + ); + run_step.dependOn(&run_cmd.step); +} + +const cflags = &.{ + "-std=c89", + "-Wall", + "-Wextra", + "-Wno-unused-parameter", +}; +const minizip_src: []const []const u8 = &.{ + "third_party/minizip/ioapi.c", + "third_party/minizip/mztools.c", + "third_party/minizip/unzip.c", + "third_party/minizip/zip.c", +}; + +const BuildInfo = struct { + lib: *std.Build.Step.Compile, + path: []const u8, + + fn filename(self: BuildInfo) []const u8 { + var split = std.mem.splitSequence(u8, std.fs.path.basename(self.path), "."); + return split.first(); + } +}; diff --git a/build.zig.zon b/build.zig.zon index 0e7fced8..590d48b9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,11 @@ .{ .name = "libxlsxwriter", .version = "1.1.6", - .dependencies = .{}, + .dependencies = .{ + .zlib = .{ + .url = "https://github.com/kassane/zlib/archive/4a99cc2bfa344c969f086fcb8c5873d80448f3e6.tar.gz", + .hash = "12209e851f7e2c6ba2f01de3e11b1771f03e49666065320abd8414aac152bfa75fae", + }, + }, } //syntax tip: zig - anon struct (json-like)