Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/_python-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
python-version: "3.11"

- name: Install Zig
run: python -m pip install ziglang==0.11.0 wheel
run: python -m pip install ziglang==0.14.0 wheel

- name: Build wheels
run: python python/make_wheels.py ${{ matrix.architecture }}-${{ matrix.os }}
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@v4
- uses: goto-bus-stop/setup-zig@v1
with:
version: 0.11.0
version: 0.14.0
- name: Get output paths
uses: kanga333/variable-mapper@master
id: map
Expand Down Expand Up @@ -64,8 +64,11 @@ jobs:
- name: Zip output executable
if: matrix.os != 'wasm'
run: zip -j fastfec-${{ matrix.os }}-${{ matrix.architecture }}-${{ inputs.version }}.zip zig-out/bin/fastfec${{ steps.map.outputs.exeExt }}
- name: Move output library
if: matrix.os != 'wasm'
- name: Move output library (windows)
if: matrix.os == 'windows'
run: mv zig-out/bin/${{ steps.map.outputs.libName }}.${{ steps.map.outputs.libExt }} libfastfec-${{ matrix.os }}-${{ matrix.architecture }}-${{ inputs.version }}.${{ steps.map.outputs.libExt }}
- name: Move output library (non-windows)
if: matrix.os != 'windows' && matrix.os != 'wasm'
run: mv zig-out/lib/${{ steps.map.outputs.libName }}.${{ steps.map.outputs.libExt }} libfastfec-${{ matrix.os }}-${{ matrix.architecture }}-${{ inputs.version }}.${{ steps.map.outputs.libExt }}
- name: Move output library (wasm)
if: matrix.os == 'wasm'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v4
- uses: goto-bus-stop/setup-zig@v1
with:
version: 0.11.0
version: 0.14.0
- name: Run zig test
run: zig build test

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
!.gitignore
!.github
!.pre-commit-config.yaml
!.git-blame-ignore-revs
bin
*.test
*.fec
Expand Down Expand Up @@ -40,4 +41,4 @@ share/python-wheels/
.installed.cfg
*.egg
*.whl
MANIFEST
MANIFEST
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,16 @@ The following was performed on an M1 Macbook Air:

### Build system

[Zig](https://ziglang.org/) is used to build and compile the project. Download and install the latest version of Zig (>=0.11.0) by following the instructions on the website (you can verify it's working by typing `zig` in the terminal and seeing help commands).
[Zig](https://ziglang.org/) is used to build and compile the project. Download and install Zig 0.14.0 or later by following the instructions on the website (you can verify it's working by typing `zig` in the terminal and seeing help commands).

### Dependencies

FastFEC has no external C dependencies. [PCRE](./src/pcre/README) is bundled with the library to ensure compatibility with Zig's build system and cross-platform compilation.
FastFEC vendors PCRE2 (`pcre2-8`) by default in `src/pcre2`, ensuring consistent builds across platforms (including wasm). Packagers may switch to system PCRE2 if desired, but no system dependency is required for normal builds.

### Requirements

- Zig >= 0.14.0 (tested on 0.14.0 and 0.15.1)
- No external C dependencies required (PCRE2 is vendored)

### Building

Expand Down
131 changes: 62 additions & 69 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
const std = @import("std");
const builtin = @import("builtin");
const CrossTarget = std.zig.CrossTarget;

pub fn linkPcre(vendored_pcre: bool, libExe: *std.build.LibExeObjStep) void {
pub fn linkPcre(vendored_pcre: bool, libExe: *std.Build.Step.Compile) void {
if (vendored_pcre) {
libExe.addCSourceFiles(&pcreSources, &buildOptions);
// Use vendored PCRE2 - we expect src/pcre2 to be present
const pcre2BuildOptions = [_][]const u8{
"-DPCRE2_CODE_UNIT_WIDTH=8",
"-DPCRE2_STATIC",
"-DHAVE_CONFIG_H",
};
libExe.addIncludePath(.{ .cwd_relative = "src/pcre2" });
libExe.addCSourceFiles(.{ .files = &pcre2Sources, .flags = &pcre2BuildOptions });
} else {
// Use system PCRE2 library
if (builtin.os.tag == .windows) {
libExe.linkSystemLibrary("pcre");
libExe.linkSystemLibrary("pcre2-8");
} else {
libExe.linkSystemLibrary("libpcre");
libExe.linkSystemLibrary("pcre2-8");
}
}
if (libExe.target.isDarwin()) {

if (builtin.os.tag == .macos) {
// useful for package maintainers
// see https://github.com/ziglang/zig/issues/13388
libExe.headerpad_max_install_names = true;
Expand All @@ -21,83 +29,62 @@ pub fn linkPcre(vendored_pcre: bool, libExe: *std.build.LibExeObjStep) void {

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

const lib_only: bool = b.option(bool, "lib-only", "Only compile the library") orelse false;
const skip_lib: bool = b.option(bool, "skip-lib", "Skip compiling the library") orelse false;
const wasm: bool = b.option(bool, "wasm", "Compile the wasm library") orelse false;
const vendored_pcre: bool = b.option(bool, "vendored-pcre", "Use vendored pcre") orelse true;
const vendored_pcre: bool = b.option(bool, "vendored-pcre", "Use vendored PCRE2") orelse true;

// Main build step
if (!lib_only and !wasm) {
const fastfec_cli = b.addExecutable(.{
.name = "fastfec",
.target = target,
.optimize = optimize,
});
const fastfec_cli = b.addExecutable(.{ .name = "fastfec", .target = target, .optimize = optimize });

fastfec_cli.linkLibC();

fastfec_cli.addCSourceFiles(&libSources, &buildOptions);
fastfec_cli.addCSourceFiles(.{ .files = &libSources, .flags = &buildOptions });
linkPcre(vendored_pcre, fastfec_cli);
fastfec_cli.addCSourceFiles(&.{
fastfec_cli.addCSourceFiles(.{ .files = &.{
"src/cli.c",
"src/main.c",
}, &buildOptions);
}, .flags = &buildOptions });
b.installArtifact(fastfec_cli);
}

if (!wasm and !skip_lib) {
// Library build step
const fastfec_lib = b.addSharedLibrary(.{
.name = "fastfec",
.target = target,
.optimize = optimize,
.version = null,
});
if (fastfec_lib.target.isDarwin()) {
const fastfec_lib = b.addSharedLibrary(.{ .name = "fastfec", .version = null, .target = target, .optimize = optimize });
if (builtin.os.tag == .macos) {
// useful for package maintainers
// see https://github.com/ziglang/zig/issues/13388
fastfec_lib.headerpad_max_install_names = true;
}
fastfec_lib.linkLibC();
fastfec_lib.addCSourceFiles(&libSources, &buildOptions);
fastfec_lib.addCSourceFiles(.{ .files = &libSources, .flags = &buildOptions });
linkPcre(vendored_pcre, fastfec_lib);
b.installArtifact(fastfec_lib);
} else if (wasm) {
// Wasm library build step
const wasm_target = CrossTarget{ .cpu_arch = .wasm32, .os_tag = .freestanding };
const fastfec_wasm = b.addSharedLibrary(.{
.name = "fastfec",
.target = wasm_target,
.optimize = optimize,
.version = null,
});
const wasm_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi });
const fastfec_wasm = b.addSharedLibrary(.{ .name = "fastfec", .version = null, .target = wasm_target, .optimize = optimize });
fastfec_wasm.entry = .disabled;
fastfec_wasm.import_symbols = true;
fastfec_wasm.linkLibC();
fastfec_wasm.addCSourceFiles(&libSources, &buildOptions);
fastfec_wasm.addCSourceFiles(.{ .files = &libSources, .flags = &buildOptions });
linkPcre(vendored_pcre, fastfec_wasm);
fastfec_wasm.addCSourceFile(.{ .file = .{
.path = "src/wasm.c",
}, .flags = &buildOptions });
fastfec_wasm.addCSourceFile(.{ .file = .{ .cwd_relative = "src/wasm.c" }, .flags = &buildOptions });
b.installArtifact(fastfec_wasm);
}

// Test step
var prev_test_step: ?*std.build.Step = null;
var prev_test_step: ?*std.Build.Step = null;
for (tests) |test_file| {
const base_file = std.fs.path.basename(test_file);
const subtest_exe = b.addExecutable(.{
.name = base_file,
});
const subtest_exe = b.addExecutable(.{ .name = base_file, .target = target, .optimize = optimize });
subtest_exe.linkLibC();
subtest_exe.addCSourceFiles(&testIncludes, &buildOptions);
subtest_exe.addCSourceFiles(.{ .files = &testIncludes, .flags = &buildOptions });
linkPcre(vendored_pcre, subtest_exe);
subtest_exe.addCSourceFile(.{
.file = .{ .path = test_file },
.flags = &buildOptions,
});
subtest_exe.addCSourceFile(.{ .file = .{ .cwd_relative = test_file }, .flags = &buildOptions });
const subtest_cmd = b.addRunArtifact(subtest_exe);
if (prev_test_step != null) {
subtest_cmd.step.dependOn(prev_test_step.?);
Expand All @@ -116,32 +103,38 @@ const libSources = [_][]const u8{
"src/csv.c",
"src/writer.c",
"src/fec.c",
"src/regex.c",
};
const pcreSources = [_][]const u8{
"src/pcre/pcre_chartables.c",
"src/pcre/pcre_byte_order.c",
"src/pcre/pcre_compile.c",
"src/pcre/pcre_config.c",
"src/pcre/pcre_dfa_exec.c",
"src/pcre/pcre_exec.c",
"src/pcre/pcre_fullinfo.c",
"src/pcre/pcre_get.c",
"src/pcre/pcre_globals.c",
"src/pcre/pcre_jit_compile.c",
"src/pcre/pcre_maketables.c",
"src/pcre/pcre_newline.c",
"src/pcre/pcre_ord2utf8.c",
"src/pcre/pcre_refcount.c",
"src/pcre/pcre_string_utils.c",
"src/pcre/pcre_study.c",
"src/pcre/pcre_tables.c",
"src/pcre/pcre_ucd.c",
"src/pcre/pcre_valid_utf8.c",
"src/pcre/pcre_version.c",
"src/pcre/pcre_xclass.c",
const pcre2Sources = [_][]const u8{
"src/pcre2/pcre2_auto_possess.c",
"src/pcre2/pcre2_chartables.c",
"src/pcre2/pcre2_compile.c",
"src/pcre2/pcre2_config.c",
"src/pcre2/pcre2_context.c",
"src/pcre2/pcre2_convert.c",
"src/pcre2/pcre2_dfa_match.c",
"src/pcre2/pcre2_error.c",
"src/pcre2/pcre2_extuni.c",
"src/pcre2/pcre2_find_bracket.c",
"src/pcre2/pcre2_match.c",
"src/pcre2/pcre2_match_data.c",
"src/pcre2/pcre2_newline.c",
"src/pcre2/pcre2_ord2utf.c",
"src/pcre2/pcre2_pattern_info.c",
"src/pcre2/pcre2_script_run.c",
"src/pcre2/pcre2_serialize.c",
"src/pcre2/pcre2_string_utils.c",
"src/pcre2/pcre2_study.c",
"src/pcre2/pcre2_substitute.c",
"src/pcre2/pcre2_substring.c",
"src/pcre2/pcre2_tables.c",
"src/pcre2/pcre2_ucd.c",
"src/pcre2/pcre2_ucptables.c",
"src/pcre2/pcre2_valid_utf.c",
"src/pcre2/pcre2_xclass.c",
};
const tests = [_][]const u8{ "src/buffer_test.c", "src/csv_test.c", "src/writer_test.c", "src/cli_test.c" };
const testIncludes = [_][]const u8{ "src/buffer.c", "src/memory.c", "src/encoding.c", "src/csv.c", "src/writer.c", "src/cli.c" };
const testIncludes = [_][]const u8{ "src/buffer.c", "src/memory.c", "src/encoding.c", "src/csv.c", "src/writer.c", "src/regex.c", "src/cli.c" };
const buildOptions = [_][]const u8{
"-std=c11",
"-pedantic",
Expand Down
2 changes: 1 addition & 1 deletion python/make_wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def write_wheel(out_dir, *, name, version, tag, metadata, description, contents)
# First clear the target directory of any stray files
if os.path.exists(LIBRARY_DIR):
shutil.rmtree(LIBRARY_DIR)
# Compile! Requires ziglang==0.11.0 to be installed
# Compile! Requires ziglang==0.14.0 to be installed
subprocess.call(
[
sys.executable,
Expand Down
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
requires = [
"setuptools",
"wheel",
"ziglang==0.11.0"
"ziglang==0.14.0"
]
2 changes: 1 addition & 1 deletion python/requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ pytest-xdist
pytest-mock
black
isort
ziglang==0.11.0
ziglang==0.14.0
-e .
24 changes: 20 additions & 4 deletions python/src/fastfec/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import logging
import os
import pathlib
import platform
from ctypes import (
CFUNCTYPE,
POINTER,
Expand All @@ -28,6 +29,8 @@
# Directories used for locating the shared library
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
PARENT_DIR = pathlib.Path(SCRIPT_DIR).parent.absolute()
# Repo root is the parent of the Python package directory (python/src)
REPO_ROOT = pathlib.Path(SCRIPT_DIR).parents[2].absolute()

# Buffer constants
BUFFER_SIZE = 1024 * 1024
Expand Down Expand Up @@ -66,13 +69,26 @@ def find_fastfec_lib():
This method tries searching in package directories, with a fallback to the local
zig build directory for development work.
"""
prefixes = ["fastfec", "libfastfec"]
# Prioritize platform-appropriate suffixes
system = platform.system().lower()
if system == "darwin":
suffixes = ["dylib", "so", "dll"]
elif system == "linux":
suffixes = ["so", "dylib", "dll"]
elif system == "windows":
suffixes = ["dll", "so", "dylib"]
else:
suffixes = ["so", "dylib", "dll"]

# Prefer libfastfec prefix for shared libraries
prefixes = ["libfastfec", "fastfec"]

suffixes = ["so", "dylib", "dll"]
directories = [
SCRIPT_DIR,
PARENT_DIR,
os.path.join(PARENT_DIR, "zig-out/lib"), # For local dev
str(REPO_ROOT),
os.path.join(str(REPO_ROOT), "zig-out", "lib"), # Root zig-out for local/dev/CI
os.path.join(str(PARENT_DIR), "zig-out", "lib"), # Legacy fallback
]

# Search in parent directory
Expand All @@ -83,7 +99,7 @@ def find_fastfec_lib():
if files:
if len(files) > 1:
logger.warning("Expected just one library file")
return os.path.join(PARENT_DIR, files[0])
return files[0]

raise LookupError("Unable to find libfastfec")

Expand Down
Loading
Loading