#| An attempt to simplify building native code for a Raku module. unit module LibraryMake; use File::Which; =begin pod This is effectively a small configure script for a Makefile. It will allow you to use the same tools to build your native code that were used to build Raku itself. Typically, this will be used in both C (to support installation using a module manager), and in a standalone C script in the C directory (to support standalone testing/building). Note that if you need additional custom configure code, you will currently need to add it to both your C and to your C. =end pod =head2 Example Usage =begin pod The below files are examples of what you would write in your own project. The src directory is merely a convention, and the C will likely be significantly different in your own project. /Build.rakumod use v6; use LibraryMake; use Shell::Command; my $libname = 'chelper'; class Build { method build($dir) { my %vars = get-vars($dir); %vars{$libname} = $*VM.platform-library-name($libname.IO); mkdir "$dir/resources" unless "$dir/resources".IO.e; mkdir "$dir/resources/libraries" unless "$dir/resources/libraries".IO.e; process-makefile($dir, %vars); my $goback = $*CWD; chdir($dir); shell(%vars); chdir($goback); } } /src/Configure.raku #!/usr/bin/env raku use v6; use LibraryMake; my $libname = 'chelper'; my %vars = get-vars('.'); %vars{$libname} = $*VM.platform-library-name($libname.IO); mkdir "resources" unless "resources".IO.e; mkdir "resources/libraries" unless "resources/libraries".IO.e; process-makefile('.', %vars); shell(%vars); say "Configure completed! You can now run '%vars' to build lib$libname."; /src/Makefile.in (Make sure you use TABs and not spaces!) .PHONY: clean test all: %DESTDIR%/resources/libraries/%chelper% clean: -rm %DESTDIR%/resources/libraries/%chelper% %DESTDIR%/*.o %DESTDIR%/resources/libraries/%chelper%: chelper%O% %LD% %LDSHARED% %LDFLAGS% %LIBS% %LDOUT%%DESTDIR%/resources/libraries/%chelper% chelper%O% chelper%O%: src/chelper.c %CC% -c %CCSHARED% %CCFLAGS% %CCOUT% chelper%O% src/chelper.c test: all prove -e "raku -Ilib" t /lib/My/Module.rakumod # ... use NativeCall; use LibraryMake; constant CHELPER = %?RESOURCES.absolute; sub foo() is native( CHELPER ) { * }; /META6.json # include the following section in your META6.json: "resources" : [ "library/chelper" ], "depends" : [ "LibraryMake" ] =end pod =head2 Functions #| Returns configuration variables. Effectively just a wrapper around $*VM.config, #| as the VM config variables are different for each backend VM. our sub get-vars(Str $destfolder --> Hash) is export { my %vars; %vars = $destfolder; if $*VM.name eq 'parrot' { %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = '-l'; # this is copied from moar - probably wrong #die "Don't know how to get platform independent '-l' (LDUSR) on Parrot"; #my $ldusr = $*VM.config; #$ldusr ~~ s/\%s//; #%vars = $ldusr; %vars = $*VM.config; } elsif $*VM.name eq 'moar' { %vars = $*VM.config; my $so = $*VM.config; $so ~~ s/^.*\%s//; %vars = $so; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; my $ldusr = $*VM.config; $ldusr ~~ s/\%s//; %vars = $ldusr; %vars = $*VM.config; %vars = $*VM.config; } elsif $*VM.name eq 'jvm' { %vars = $*VM.config; %vars = '.' ~ $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = "-o"; # this looks wrong? %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = $*VM.config; %vars = 'make'; %vars = '-l'; # this is copied from moar - probably wrong #die "Don't know how to get platform independent '-l' (LDUSR) on JVM"; #my $ldusr = $*VM.config; #$ldusr ~~ s/\%s//; #%vars = $ldusr; %vars = $*VM.config; } else { die "Unknown VM; don't know how to build"; } for %vars.kv -> $k, $v { %vars{$k} [R//]= %*ENV{$k}; } return %vars; } #| Takes '$folder/Makefile.in' and writes out '$folder/Makefile'. %vars should #| be the result of C above. our sub process-makefile(Str $folder, %vars) is export { my $makefile = slurp($folder~'/Makefile.in'); for %vars.kv -> $k, $v { $makefile ~~ s:g/\%$k\%/$v/; } if ( $folder.IO.w() ) { spurt($folder ~ '/Makefile', $makefile); } else { die "$folder is not writable"; } } #| Calls C and C for you to generate '$folder/Makefile', #| then runs your system's C to build it. our sub make(Str $folder, Str $destfolder) is export { my %vars = get-vars($destfolder); process-makefile($folder, %vars); my $goback = $*CWD; chdir($folder); my $proc = shell(%vars); while $proc.exitcode == -1 { # busy wait # (shell blocks, so this is in theory not needed) } if $proc.exitcode != 0 { die "make exited with signal "~$proc.exitcode; } chdir($goback); } sub can-compile(%vars) { so %vars && which(%vars); } sub can-link(%vars) { so %vars && which(%vars); } sub can-make(%vars = get-vars('.')) { so %vars && which(%vars); } our sub build-tools-installed() is export { my %vars = get-vars('.'); can-compile(%vars) && can-link(%vars) && can-make(%vars) }