NAME

Affix::Build - Robust, Polyglot Build System for FFI Extensions

SYNOPSIS

use Affix::Build;

# Builds a shared library using Rust's native toolchain configuration.
my $rust = Affix::Build->new( name => 'my_rust_lib' );
$rust->add('src/logic.rs');
my $lib_path = $rust->compile_and_link(); # Returns path to .so/.dll (e.g., /tmp/.../my_rust_lib.dll)

# Compiles C and Go separately, then links them into a single library.
my $mix = Affix::Build->new(
    name      => 'hybrid_lib',
    build_dir => 'build/',
    flags     => { cflags => '-O3', ldflags => '-L/opt/lib' },
    debug     => 1
);

$mix->add('src/wrapper.c', flags => '-DDEBUG'); # Per-file overrides
$mix->add('src/core.go');

my $dll = $mix->link();

DESCRIPTION

Affix::Build is a cross-platform compilation utility designed to generate shared libraries (.dll, .so, .dylib) from source code in over 15 different programming languages.

It is specifically engineered to support Foreign Function Interface (FFI) development. It abstracts away the complexity of invoking various compilers, normalizing object file extensions, handling platform-specific linker flags (such as MinGW vs MSVC on Windows), and ensuring that language runtimes (like the Go GC or .NET Runtime) are correctly initialized.

To be honest, this might be an easy backdoor way to get the functionality of an Inline module but I wrote it to test against compiled libs in the Affix test suite.

Build Strategies

The compiler automatically selects the best build strategy based on the input sources:

1. Native/Dynamic Strategy (Single Language)

If you provide source files for only one language, Affix::Build delegates the entire build process to that language's native toolchain (e.g., `go build -buildmode=c-shared`, `rustc --crate-type cdylib`, `dotnet publish`).

This is the preferred method as it guarantees the language's standard library and runtime environment are configured exactly as the language maintainers intended.

2. Polyglot/Static Strategy (Mixed Languages)

If you provide source files from multiple languages (e.g., C calling into Rust), the compiler switches to an aggregation strategy:

1. It instructs every compiler to output a Static Library (.a / .lib) or Object File (.o / .obj).
2. It aggregates these artifacts.

CONSTRUCTOR

my $c = Affix::Build->new( %params );

Creates a new compiler instance.

name (Default: 'affix_lib')

The base name of the resulting library. The compiler will automatically append the OS-specific extension (.dll on Windows, .dylib on macOS, .so on Linux).

build_dir (Default: Temporary Directory)

The directory where intermediate artifacts and the final library will be written. If not provided, a temporary directory is created via Path::Tiny and cleaned up when the object is destroyed.

debug (Default: 0)

If set to true, the exact system commands executed by the compiler will be printed to STDERR.

flags (Default: {})

Hashref of global flags: cflags, cxxflags, ldflags.

os (Default: $^O)

Override the detected operating system (useful for testing).

METHODS

add( ... )

$c->add( 'path/to/file.c' );
$c->add( 'path/to/logic.rs' );
$c->add( 'file.c', flags => '-DDEBUG' );
$c->add( \'int main(){}', lang => 'c' );

Adds a source file to the build manifest. The compiler auto-detects the language based on the file extension.

You can also pass raw source code as a SCALAR reference; in this case, the lang argument is required (e.g., 'c', 'rust', 'go'). See "SUPPORTED LANGUAGES" for a list of recognized extensions.

Optionally, this method accepts a flags argument (string or arrayref) to pass specific flags to the compiler for this file.

See "SUPPORTED LANGUAGES" for a list of recognized extensions.

compile_and_link( )

my $path_object = $c->compile_and_link();

Performs the build process:

1. Inspects added sources to determine the Build Strategy.
2. Compiles all sources to their appropriate intermediate or final formats.

Returns a Path::Tiny object pointing to the generated shared library. Dies with a detailed error message (including STDOUT/STDERR of the failed command) if compilation fails.

link( )

An alias for "compile_and_link( )" but only compiles and links if the state hasn't changed since the last build.

SUPPORTED LANGUAGES

Affix::Build attempts to locate the necessary binaries in your PATH.

C / C++

Automatically handles -fPIC on non-Windows platforms.

Supported extensions: .c, .cpp, .cxx, .cc

Known compilers: cc, gcc, clang, cl (MSVC), c++, g++.

Rust

Extensions: .rs

Compiler: rustc

Windows Note: If running under Strawberry Perl (MinGW), you must have the GNU ABI target installed via rustup:

> rustup target add x86_64-pc-windows-gnu

C# / F#

Uses **NativeAOT** to compile C# code directly to machine code. The source must use [UnmanagedCallersOnly].

Extensions: .cs, .fs

Compiler: dotnet (SDK 8.0 or newer required)

Go

Extensions: .go

Compiler: go or gccgo

Notes:

  • In Polyglot mode, requires gccgo or standard go with c-archive support.

  • The Go runtime spins up background threads (for GC and scheduling) that do not shut down cleanly when a shared library is unloaded. This often causes access violations on Windows during program exit.

    Affix attempts to detect Go libraries (by looking for the _cgo_dummy_export symbol) and keep them in memory to prevent this crash. However, if you still encounter segmentation faults during global destruction, you can force a safe exit using POSIX::_exit(0) which skips global destructors.

Zig

Extensions: .zig

Compiler: zig

Fortran

Extensions: .f, .f90, .f95, .for

Compiler: gfortran, ifx, ifort

Assembly

Extensions: .asm (Intel syntax), .s (AT&T/GNU syntax)

Compilers: nasm (for .asm), System cc (for .s)

Notes: Correctly handles ARM64 (AArch64) via the system compiler and x86_64 via NASM.

Other Languages

Support is also included for:

Odin (.odin)
D (.d)
Nim (.nim)
V (.v)
Swift (.swift)
Pascal (.pas, .pp) - via Free Pascal
Crystal (.cr)
Haskell (.hs) - via GHC
Cobol (.cob, .cbl) - via GnuCOBOL
OCaml (.ml)
Eiffel (.e) - via SmartEiffel
Futhark (.fut) - Transpiles to C

Platform Notes

Windows (MinGW / Strawberry Perl)

This environment is "Polyglot Hard Mode." Affix::Build performs several tricks to make it work:

2. Whole Archive: When linking static libraries (.a) into a shared library, passes -Wl,--whole-archive to ensure symbols are exported.
3. Exports: Automatically adds -Wl,--export-all-symbols when linking to ensure Fortran/Assembly symbols are visible without explicit decoration.

AUTHOR

Sanko Robinson <sanko@cpan.org>

COPYRIGHT

Copyright (C) 2023-2026 by Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.