Affix
view release on metacpan or search on metacpan
t/017_affix_build.t view on Meta::CPAN
return ( $out =~ /^8\./m );
}
sub check_rust_gnu () {
return 0 unless bin_path('rustc');
# If not windows, standard rustc is fine
return 1 if $^O ne 'MSWin32';
# On Windows, we need to prove the GNU target works
my $tmp = Path::Tiny->tempfile( SUFFIX => '.rs' );
$tmp->spew('fn main() {}');
my $cmd = "rustc --target x86_64-pc-windows-gnu \"$tmp\" -o \"$tmp.exe\" 2>&1";
my $out = `$cmd`;
return ( $? == 0 );
}
sub enjoin( $lib, @symbols ) {
my $path = $lib->stringify;
my $dll = DynaLoader::dl_load_file( $path, 0 );
unless ($dll) {
fail( "Failed to load library '$path': " . DynaLoader::dl_error() );
return;
}
for my $sym (@symbols) {
if ( DynaLoader::dl_find_symbol( $dll, $sym ) ) {
pass( "Symbol '$sym' found in " . $lib->basename );
}
else {
fail( "Symbol '$sym' NOT found in " . $lib->basename );
}
}
DynaLoader::dl_unload_file($dll) if $^O eq 'MSWin32' && defined &DynaLoader::dl_unload_file;
}
sub run_test ( $lang, $name, $code, $sym, $bin_req, $validator //= () ) {
subtest "$name compilation" => sub {
my $bin = ( $lang eq 'c' ) ? $Config{cc} : $bin_req;
unless ( bin_path($bin) ) {
skip_all "No $name compiler found ($bin)";
return;
}
if ($validator) {
unless ( $validator->() ) {
skip_all "$name toolchain unmet";
return;
}
}
#
my $ext
= $lang eq 'rust' ? 'rs' :
$lang eq 'csharp' ? 'cs' :
$lang eq 'fsharp' ? 'fs' :
$lang eq 'fortran' ? 'f90' :
$lang eq 'pascal' ? 'pas' :
$lang eq 'crystal' ? 'cr' :
$lang eq 'assembly' ? 'asm' :
$lang eq 'cobol' ? 'cbl' :
$lang;
my $src = $TMP_DIR->child("test_$lang.$ext");
$src->spew_utf8($code);
#
my $c = Affix::Build->new( build_dir => $TMP_DIR, name => "${lang}_lib" );
$c->add($src);
try { $c->compile_and_link() }
catch ($err) {
skip_all 'Link failed (toolchain issue?): ' . $err;
return;
}
pass 'Linked successfully';
#
my $lib = $c->libname;
ok $lib->exists, 'Library created: ' . $lib->basename;
#
my $dll = DynaLoader::dl_load_file( "$lib", 0 );
if ($dll) {
if ( DynaLoader::dl_find_symbol( $dll, $sym ) ) {
pass("Symbol '$sym' found");
}
else {
fail("Symbol '$sym' NOT found in lib");
}
# Attempt unload
DynaLoader::dl_unload_file($dll) if ( $^O eq 'MSWin32' ? $lang ne 'go' : 0 ) && defined &DynaLoader::dl_unload_file;
}
else {
diag( "Load failed: " . DynaLoader::dl_error() );
}
#
ok my $fn = wrap( $lib, $sym, [ Int32, Int32 ], Int32 ), 'wrap';
is $fn->( 3, 7 ), 10, 'call';
};
}
#
run_test( 'c', 'C', <<~'', 'add_c', 'cc' );
#include <stdio.h>
#ifdef _WIN32
__declspec(dllexport)
#endif
int add_c(int a, int b) {
return a + b;
}
run_test( 'cpp', 'C++', <<~'', 'add_cpp', 'c++' );
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
extern "C" {
EXPORT int add_cpp(int a, int b) {
return a + b;
}
}
run_test( 'csharp', 'C#', <<~'', 'add_cs', 'dotnet', \&check_dotnet );
using System.Runtime.InteropServices;
namespace T {
public class C {
[UnmanagedCallersOnly(EntryPoint="add_cs")]
t/017_affix_build.t view on Meta::CPAN
begin
add_pas := a + b;
end;
exports add_pas;
begin end.
run_test( 'cr', 'Crystal', <<~'', 'add_cr', 'crystal' );
fun add_cr(a : Int32, b : Int32) : Int32
a + b
end
run_test( 'swift', 'Swift', <<~'', 'add_swift', 'swiftc' );
@_cdecl("add_swift")
public func add_swift(a: Int32, b: Int32) -> Int32 {
return a + b
}
run_test( 'assembly', 'Assembly', $Config{archname} =~ /arm64|aarch64/ ? <<~'' : $^O eq 'MSWin32' ? <<~'': <<~'', 'add_asm', 'nasm' );
; ARM64: add w0, w0, w1
.global add_asm
.text
.align 2
add_asm:
add w0, w0, w1
ret
; Win64 x86_64: RCX + RDX -> RAX
global add_asm
section .text
add_asm:
mov eax, ecx
add eax, edx
ret
; SysV x86_64: RDI + RSI -> RAX
global add_asm
section .text
add_asm:
mov eax, edi
add eax, esi
ret
run_test( 'cobol', 'Cobol', <<~'', 'add_cob', 'cobc' );
IDENTIFICATION DIVISION.
PROGRAM-ID. add_cob.
DATA DIVISION.
LINKAGE SECTION.
01 A PIC 9(9) USAGE COMP-5.
01 B PIC 9(9) USAGE COMP-5.
01 R PIC 9(9) USAGE COMP-5.
PROCEDURE DIVISION USING A, B, R.
ADD A TO B GIVING R.
GOBACK.
END PROGRAM add_cob.
subtest 'Polyglot: Number Cruncher (C + Fortran + ASM)' => sub {
skip_all "Missing compilers" unless bin_path( $Config{cc} ) && bin_path('gfortran');
# C is our orchestrator
my $c_src = $TMP_DIR->child('math_core.c');
$c_src->spew_utf8(<<~'C');
#include <stdio.h>
#ifdef _WIN32
__declspec(dllexport)
#endif
int core_version() { return 1; }
C
# Fortran does the math
my $f_src = $TMP_DIR->child('math_algos.f90');
$f_src->spew_utf8(<<~'F90');
function fortran_add(a, b) bind(c, name='fortran_add')
use iso_c_binding
integer(c_int), value :: a, b
integer(c_int) :: fortran_add
fortran_add = a + b
end function
F90
# Assembly for optimization
my $asm_bin;
my $asm_src;
my $asm_file_name;
if ( $Config{archname} =~ /arm64|aarch64/ ) {
$asm_bin = $Config{cc};
$asm_file_name = 'fast.s';
$asm_src = <<~'' }
.global asm_inc
.text
.align 2
asm_inc:
add w0, w0, #1
ret
elsif ( $^O eq 'MSWin32' ) {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, ecx
inc eax
ret
else {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, edi
inc eax
ret
skip_all "Missing Assembler ($asm_bin)" unless bin_path($asm_bin);
my $asm_file = $TMP_DIR->child($asm_file_name);
$asm_file->spew_utf8($asm_src);
#
my $compiler = Affix::Build->new( name => 'number_cruncher', build_dir => $TMP_DIR );
$compiler->add($c_src);
$compiler->add($f_src);
$compiler->add($asm_file);
ok( lives { $compiler->link() }, 'Linked Number Cruncher' ) or note $@;
ok( $compiler->libname->exists, 'Library exists' );
enjoin( $compiler->libname, 'core_version', 'fortran_add', 'asm_inc' );
};
subtest 'Polyglot: Modern Stack (C++ + Rust + Zig)' => sub {
skip_all 'Missing compilers' unless bin_path('g++') && bin_path('rustc') && bin_path('zig');
skip_all 'Rust/MinGW target missing' unless check_rust_gnu();
# C++ for ease of ABI
my $cpp_src = $TMP_DIR->child('interface.cpp');
$cpp_src->spew_utf8(<<~'');
extern "C" {
#ifdef _WIN32
__declspec(dllexport)
#endif
int cpp_interface() { return 2025; }
}
# Rust for safety
my $rs_src = $TMP_DIR->child('safety.rs');
$rs_src->spew_utf8(<<~'');
#[no_mangle]
pub extern "C" fn rust_safe_add(a: i32, b: i32) -> i32 {
a + b
}
# Zig for logic
my $zig_src = $TMP_DIR->child('logic.zig');
$zig_src->spew_utf8(<<~'');
export fn zig_calc() i32 {
return 42;
}
#
my $compiler = Affix::Build->new( name => 'modern_stack', build_dir => $TMP_DIR );
$compiler->add($cpp_src);
$compiler->add($rs_src);
$compiler->add($zig_src);
ok( lives { $compiler->link() }, 'Linked Modern Stack' ) or note $@;
enjoin( $compiler->libname, 'cpp_interface', 'rust_safe_add', 'zig_calc' );
};
subtest 'Polyglot: Kitchen Sink (C, C++, Rust, Zig, Dlang, Fortran, ASM)' => sub {
my @reqs = qw(g++ zig dmd gfortran);
push @reqs, $Config{cc}; # System CC
push @reqs, ( $Config{archname} =~ /arm64/ ? $Config{cc} : 'nasm' );
push @reqs, 'rustc';
for my $bin (@reqs) {
skip_all "Missing $bin" unless bin_path($bin);
}
skip_all 'Rust/MinGW target missing' unless check_rust_gnu();
my $c = Affix::Build->new( name => 'mega_lib', build_dir => $TMP_DIR );
#
my $f1 = $TMP_DIR->child('f1.c');
$f1->spew_utf8(<<~'');
#ifdef _WIN32
__declspec(dllexport)
#endif
int func_c( ) { return 1; }
$c->add($f1);
#
my $f2 = $TMP_DIR->child('f2.cpp');
$f2->spew_utf8(<<~'');
extern "C" {
#ifdef _WIN32
__declspec(dllexport)
#endif
int func_cpp( ) { return 2; }
}
$c->add($f2);
#
my $f3 = $TMP_DIR->child('f3.rs');
$f3->spew_utf8(<<~'');
#[no_mangle]
pub extern "C" fn func_rs( )->i32{ 3 }
$c->add($f3);
#
my $f4 = $TMP_DIR->child('f4.zig');
$f4->spew_utf8(<<~'');
export fn func_zig() i32 { return 4; }
$c->add($f4);
#
my $f5 = $TMP_DIR->child('f5.d');
$f5->spew_utf8( ( $^O eq 'MSWin32' ? <<~'' : '' ) . <<~'' );
import core.sys.windows.dll;
mixin SimpleDllMain;
export
extern(C) int func_d() { return 5; }
$c->add($f5);
#
my $f6 = $TMP_DIR->child('f6.f90');
$f6->spew_utf8(<<~'');
function func_f() bind(c, name='func_f')
use iso_c_binding
integer(c_int) :: func_f
func_f=6
end function
$c->add($f6);
#
my $asm_ext = ( $Config{archname} =~ /arm64/ ) ? 's' : 'asm';
my $f7 = $TMP_DIR->child("f7.$asm_ext");
$f7->spew_utf8( ( $^O eq 'MSWin32' || $Config{archname} !~ /arm64/ ) ? <<~'' : <<~'' );
; x86/x64
global func_asm
section .text
func_asm:
mov eax, 7
ret
; ARM64
.global func_asm
.text
func_asm:
mov w0, #7
ret
$c->add($f7);
#
ok( lives { $c->link() }, 'Linked Kitchen Sink' ) or note $@;
#
enjoin( $c->libname, 'func_c', 'func_cpp', 'func_rs', 'func_zig', 'func_d', 'func_f', 'func_asm' );
};
#
done_testing;
( run in 0.699 second using v1.01-cache-2.11-cpan-5a3173703d6 )