├── .github └── workflows │ ├── nixos.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── META6.json ├── README.md ├── lib └── LibraryMake.rakumod └── t ├── 01-basic.t ├── Makefile.in └── test.c /.github/workflows/nixos.yml: -------------------------------------------------------------------------------- 1 | name: LinuxNix 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - 'releases/**' 9 | pull_request: 10 | jobs: 11 | launch: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: cachix/install-nix-action@v25 16 | with: 17 | nix_path: nixpkgs=channel:nixos-unstable 18 | - run: | 19 | nix-shell -p rakudo nqp moarvm perl zef stdenv zlib openssl cacert git curl wget --run "zef --debug install ." 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | 10 | jobs: 11 | raku: 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | - macOS-latest 17 | raku-version: 18 | - 'latest' 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: Raku/setup-raku@v1 23 | with: 24 | raku-version: ${{ matrix.raku-version }} 25 | - name: Test and install dependencies 26 | run: zef install . 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test.exe 2 | test.obj 3 | 4 | # Raku stuff 5 | .precomp/ 6 | .idea/ 7 | *.iml 8 | 9 | # fez 10 | sdist/ 11 | 12 | # emacs 13 | *~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Andrew Egeler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LibraryMake", 3 | "description": "An attempt to simplify native compilation", 4 | "version": "1.0.5", 5 | "perl": "6.*", 6 | "meta-version": 1, 7 | "auth": "zef:jjmerelo", 8 | "depends": [ 9 | "Shell::Command", 10 | "File::Which" 11 | ], 12 | "provides": { 13 | "LibraryMake": "lib/LibraryMake.rakumod" 14 | }, 15 | "license": "MIT", 16 | "source-url": "git://github.com/retupmoca/P6-LibraryMake.git" 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LibraryMake 2 | ------------------ 3 | 4 | [![OS 5 | tests](https://github.com/retupmoca/P6-LibraryMake/actions/workflows/test.yml/badge.svg)](https://github.com/retupmoca/P6-LibraryMake/actions/workflows/test.yml) [![LinuxNix](https://github.com/retupmoca/P6-LibraryMake/actions/workflows/nixos.yml/badge.svg)](https://github.com/retupmoca/P6-LibraryMake/actions/workflows/nixos.yml) 6 | 7 | An attempt to simplify building native code for a 8 | [Raku](https://raku.org) module. It should work on all platforms, as long as 9 | specified tools are available locally. 10 | 11 | This is effectively a small configure script for a Makefile. It will allow you to 12 | use the same tools to build your native code that were used to build Raku itself. 13 | 14 | Typically, this will be used in both `Build.rakumod` (to support installation using a 15 | module manager), and in a standalone `Configure.raku` script in the `src` directory 16 | (to support standalone testing/building). Note that if you need additional 17 | custom configure code, you will currently need to add it to both your `Build.rakumod` 18 | and to your `Configure.raku`. 19 | 20 | Example Usage 21 | ------------- 22 | 23 | The below files are examples of what you would write in your own project. 24 | The `src` directory is merely a convention, and the `Makefile.in` will likely be 25 | significantly different in your own project. 26 | 27 | /Build.rakumod 28 | 29 | ```raku 30 | use LibraryMake; 31 | use Shell::Command; 32 | 33 | my $libname = 'chelper'; 34 | 35 | class Build { 36 | method build($dir) { 37 | my %vars = get-vars($dir); 38 | %vars{$libname} = $*VM.platform-library-name($libname.IO); 39 | mkdir "$dir/resources" unless "$dir/resources".IO.e; 40 | mkdir "$dir/resources/libraries" unless "$dir/resources/libraries".IO.e; 41 | process-makefile($dir, %vars); 42 | my $goback = $*CWD; 43 | chdir($dir); 44 | shell(%vars); 45 | chdir($goback); 46 | } 47 | } 48 | ``` 49 | 50 | `src/Configure.raku` 51 | 52 | ```raku 53 | #!/usr/bin/env raku 54 | use LibraryMake; 55 | 56 | my $libname = 'chelper'; 57 | my %vars = get-vars('.'); 58 | %vars{$libname} = $*VM.platform-library-name($libname.IO); 59 | mkdir "resources" unless "resources".IO.e; 60 | mkdir "resources/libraries" unless "resources/libraries".IO.e; 61 | process-makefile('.', %vars); 62 | shell(%vars); 63 | 64 | say "Configure completed! You can now run '%vars' to build lib$libname."; 65 | ``` 66 | 67 | `src/Makefile.in` (Make sure you use TABs and not spaces!) 68 | 69 | ```Makefile 70 | .PHONY: clean test 71 | 72 | all: %DESTDIR%/resources/libraries/%chelper% 73 | 74 | clean: 75 | -rm %DESTDIR%/resources/libraries/%chelper% %DESTDIR%/*.o 76 | 77 | %DESTDIR%/resources/libraries/%chelper%: chelper%O% 78 | %LD% %LDSHARED% %LDFLAGS% %LIBS% %LDOUT%%DESTDIR%/resources/libraries/%chelper% chelper%O% 79 | 80 | chelper%O%: src/chelper.c 81 | %CC% -c %CCSHARED% %CCFLAGS% %CCOUT% chelper%O% src/chelper.c 82 | 83 | test: all 84 | prove -e "raku -Ilib" t 85 | ``` 86 | 87 | /lib/My/Module.rakumod 88 | 89 | ```raku 90 | # ... 91 | 92 | use NativeCall; 93 | use LibraryMake; 94 | 95 | constant CHELPER = %?RESOURCES.absolute; 96 | 97 | sub foo() is native( CHELPER ) { * }; 98 | ``` 99 | 100 | Include the following section in your META6.json: 101 | 102 | 103 | ```JSON 104 | "resources" : [ 105 | "library/chelper" 106 | ], 107 | "depends" : [ 108 | "LibraryMake" 109 | ] 110 | ``` 111 | 112 | Functions 113 | --------- 114 | 115 | ### sub get-vars 116 | 117 | ``` 118 | sub get-vars( 119 | Str $destfolder 120 | ) returns Hash 121 | ``` 122 | 123 | Returns configuration variables. Effectively just a wrapper around $*VM.config, as the VM config variables are different for each backend VM. 124 | 125 | ### sub process-makefile 126 | 127 | ``` 128 | sub process-makefile( 129 | Str $folder, 130 | %vars 131 | ) returns Mu 132 | ``` 133 | 134 | Takes '$folder/Makefile.in' and writes out '$folder/Makefile'. %vars should be the result of `get-vars` above. 135 | 136 | ### sub make 137 | 138 | ``` 139 | sub make( 140 | Str $folder, 141 | Str $destfolder 142 | ) returns Mu 143 | ``` 144 | 145 | Calls `get-vars` and `process-makefile` for you to generate '$folder/Makefile', then runs your system's 'make' to build it. 146 | 147 | 148 | ### sub build-tools-installed() 149 | ``` 150 | sub build-tools-installed( 151 | ) returns Bool 152 | ``` 153 | 154 | Returns True if the configured compiler(CC), linker(LD) and make program(MAKE) have been installed on this sytem system. 155 | 156 | 157 | 158 | ## Change log 159 | 160 | * [1.0.4](https://github.com/retupmoca/P6-LibraryMake/releases/tag/v1.0.4) Reinstates macOS test and eliminates test that fails in it. 161 | * [1.0.3](https://github.com/retupmoca/P6-LibraryMake/releases/tag/v1.0.3) Fixes files included in zef. 162 | * [1.0.2](https://github.com/retupmoca/P6-LibraryMake/releases/tag/v1.0.2) Fixes test error when `prove6` is installed as test runner and 163 | picked up by `zef`. 164 | * [1.0.1](https://github.com/retupmoca/P6-LibraryMake/releases/tag/v1.0.1) Checks that the directory it's writing is writable, errors if it 165 | does not 166 | * [1.0.0](https://github.com/retupmoca/P6-LibraryMake/releases/tag/v1.0.0 167 | ) Original version, newly released to the zef ecosystem. 168 | -------------------------------------------------------------------------------- /lib/LibraryMake.rakumod: -------------------------------------------------------------------------------- 1 | #| An attempt to simplify building native code for a Raku module. 2 | unit module LibraryMake; 3 | 4 | use File::Which; 5 | 6 | =begin pod 7 | 8 | This is effectively a small configure script for a Makefile. It will allow you to 9 | use the same tools to build your native code that were used to build Raku itself. 10 | 11 | Typically, this will be used in both C (to support installation using a 12 | module manager), and in a standalone C script in the C directory 13 | (to support standalone testing/building). Note that if you need additional 14 | custom configure code, you will currently need to add it to both your C 15 | and to your C. 16 | 17 | =end pod 18 | 19 | =head2 Example Usage 20 | 21 | =begin pod 22 | The below files are examples of what you would write in your own project. 23 | The src directory is merely a convention, and the C will likely be 24 | significantly different in your own project. 25 | 26 | /Build.rakumod 27 | 28 | use v6; 29 | use LibraryMake; 30 | use Shell::Command; 31 | 32 | my $libname = 'chelper'; 33 | 34 | class Build { 35 | method build($dir) { 36 | my %vars = get-vars($dir); 37 | %vars{$libname} = $*VM.platform-library-name($libname.IO); 38 | mkdir "$dir/resources" unless "$dir/resources".IO.e; 39 | mkdir "$dir/resources/libraries" unless "$dir/resources/libraries".IO.e; 40 | process-makefile($dir, %vars); 41 | my $goback = $*CWD; 42 | chdir($dir); 43 | shell(%vars); 44 | chdir($goback); 45 | } 46 | } 47 | 48 | /src/Configure.raku 49 | 50 | #!/usr/bin/env raku 51 | use v6; 52 | use LibraryMake; 53 | 54 | my $libname = 'chelper'; 55 | my %vars = get-vars('.'); 56 | %vars{$libname} = $*VM.platform-library-name($libname.IO); 57 | mkdir "resources" unless "resources".IO.e; 58 | mkdir "resources/libraries" unless "resources/libraries".IO.e; 59 | process-makefile('.', %vars); 60 | shell(%vars); 61 | 62 | say "Configure completed! You can now run '%vars' to build lib$libname."; 63 | 64 | /src/Makefile.in (Make sure you use TABs and not spaces!) 65 | 66 | .PHONY: clean test 67 | 68 | all: %DESTDIR%/resources/libraries/%chelper% 69 | 70 | clean: 71 | -rm %DESTDIR%/resources/libraries/%chelper% %DESTDIR%/*.o 72 | 73 | %DESTDIR%/resources/libraries/%chelper%: chelper%O% 74 | %LD% %LDSHARED% %LDFLAGS% %LIBS% %LDOUT%%DESTDIR%/resources/libraries/%chelper% chelper%O% 75 | 76 | chelper%O%: src/chelper.c 77 | %CC% -c %CCSHARED% %CCFLAGS% %CCOUT% chelper%O% src/chelper.c 78 | 79 | test: all 80 | prove -e "raku -Ilib" t 81 | 82 | /lib/My/Module.rakumod 83 | 84 | # ... 85 | 86 | use NativeCall; 87 | use LibraryMake; 88 | 89 | constant CHELPER = %?RESOURCES.absolute; 90 | 91 | sub foo() is native( CHELPER ) { * }; 92 | 93 | /META6.json 94 | 95 | # include the following section in your META6.json: 96 | "resources" : [ 97 | "library/chelper" 98 | ], 99 | "depends" : [ 100 | "LibraryMake" 101 | ] 102 | 103 | =end pod 104 | 105 | =head2 Functions 106 | 107 | #| Returns configuration variables. Effectively just a wrapper around $*VM.config, 108 | #| as the VM config variables are different for each backend VM. 109 | our sub get-vars(Str $destfolder --> Hash) is export { 110 | my %vars; 111 | %vars = $destfolder; 112 | if $*VM.name eq 'moar' { 113 | %vars = $*VM.config; 114 | my $so = $*VM.config; 115 | $so ~~ s/^.*\%s//; 116 | %vars = $so; 117 | %vars = $*VM.config; 118 | %vars = $*VM.config; 119 | %vars = $*VM.config; 120 | %vars = $*VM.config; 121 | 122 | %vars = $*VM.config; 123 | %vars = $*VM.config; 124 | %vars = $*VM.config; 125 | %vars = $*VM.config; 126 | %vars = $*VM.config; 127 | my $ldusr = $*VM.config; 128 | $ldusr ~~ s/\%s//; 129 | %vars = $ldusr; 130 | 131 | %vars = $*VM.config; 132 | 133 | %vars = $*VM.config; 134 | } 135 | elsif $*VM.name eq 'jvm' { 136 | %vars = $*VM.config; 137 | %vars = '.' ~ $*VM.config; 138 | %vars = $*VM.config; 139 | %vars = $*VM.config; 140 | %vars = "-o"; # this looks wrong? 141 | %vars = $*VM.config; 142 | 143 | %vars = $*VM.config; 144 | %vars = $*VM.config; 145 | %vars = $*VM.config; 146 | %vars = $*VM.config; 147 | %vars = $*VM.config; 148 | 149 | %vars = 'make'; 150 | 151 | %vars = '-l'; 152 | # this is copied from moar - probably wrong 153 | #die "Don't know how to get platform independent '-l' (LDUSR) on JVM"; 154 | #my $ldusr = $*VM.config; 155 | #$ldusr ~~ s/\%s//; 156 | #%vars = $ldusr; 157 | 158 | %vars = $*VM.config; 159 | } 160 | else { 161 | die "Unknown VM; don't know how to build"; 162 | } 163 | 164 | for %vars.kv -> $k, $v { 165 | %vars{$k} [R//]= %*ENV{$k}; 166 | } 167 | 168 | return %vars; 169 | } 170 | 171 | #| Takes '$folder/Makefile.in' and writes out '$folder/Makefile'. %vars should 172 | #| be the result of C above. 173 | our sub process-makefile(Str $folder, %vars) is export { 174 | my $makefile = slurp($folder~'/Makefile.in'); 175 | for %vars.kv -> $k, $v { 176 | $makefile ~~ s:g/\%$k\%/$v/; 177 | } 178 | if ( $folder.IO.w() ) { 179 | spurt($folder ~ '/Makefile', $makefile); 180 | } else { 181 | die "$folder is not writable"; 182 | } 183 | } 184 | 185 | #| Calls C and C for you to generate '$folder/Makefile', 186 | #| then runs your system's C to build it. 187 | our sub make(Str $folder, Str $destfolder) is export { 188 | my %vars = get-vars($destfolder); 189 | process-makefile($folder, %vars); 190 | 191 | my $goback = $*CWD; 192 | chdir($folder); 193 | my $proc = shell(%vars); 194 | while $proc.exitcode == -1 { 195 | # busy wait 196 | # (shell blocks, so this is in theory not needed) 197 | } 198 | if $proc.exitcode != 0 { 199 | die "make exited with signal "~$proc.exitcode; 200 | } 201 | chdir($goback); 202 | } 203 | 204 | sub can-compile(%vars) { 205 | so %vars && which(%vars); 206 | } 207 | sub can-link(%vars) { 208 | so %vars && which(%vars); 209 | } 210 | sub can-make(%vars = get-vars('.')) { 211 | so %vars && which(%vars); 212 | } 213 | our sub build-tools-installed() is export { 214 | my %vars = get-vars('.'); 215 | can-compile(%vars) && can-link(%vars) && can-make(%vars) 216 | } 217 | -------------------------------------------------------------------------------- /t/01-basic.t: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | use LibraryMake; 4 | 5 | constant FLAG = "-fPIC"; 6 | %*ENV = FLAG; 7 | my %vars = get-vars('.'); 8 | 9 | subtest "Sanity checks", { 10 | ok %vars:exists, "Can get vars"; 11 | ok (%vars eq FLAG), "ENV overrides VM defaults"; 12 | } 13 | 14 | subtest "Can create Makefile", { 15 | if build-tools-installed() { 16 | lives-ok { process-makefile('t', %vars) }, "Process makefile didn't die"; 17 | ok ("t/Makefile".IO ~~ :f), "Makefile was created"; 18 | chdir("t"); 19 | my $make-output = shell(%vars, :out, :err); 20 | is $make-output.err.slurp(:close), ""; 21 | ok (("test" ~ %vars).IO ~~ :f), "Object file created"; 22 | ok (("test" ~ %vars).IO ~~ :f), "Binary was created"; 23 | ok qqx/.{ $*SPEC.dir-sep }test%vars/ ~~ /^Hello ' ' world\!\n$/, 24 | "Binary runs!"; 25 | } 26 | else { 27 | skip 28 | "Build tools are not installed (CC:%vars, LD:%vars, MAKE:%vars)", 29 | 5; 30 | } 31 | } 32 | 33 | if ( !$*DISTRO.is-win && $*DISTRO.name ne "macos") { 34 | warn $*DISTRO.name; 35 | subtest "Errors correctly if it can't", { 36 | my $this-dir = ".".IO; 37 | my $keep-mode = $this-dir.mode; 38 | $this-dir.chmod: 0x555; 39 | throws-like { process-makefile('t', %vars) }, X::AdHoc; 40 | $this-dir.chmod: $keep-mode; 41 | } 42 | } 43 | 44 | for { 45 | $_.IO.unlink; 46 | } 47 | "Makefile".IO.unlink; 48 | 49 | done-testing; 50 | -------------------------------------------------------------------------------- /t/Makefile.in: -------------------------------------------------------------------------------- 1 | all: %DESTDIR%/test%EXE% 2 | 3 | %DESTDIR%/test%EXE%: test%O% 4 | %CC% %LDFLAGS% %LDOUT%%DESTDIR%/test%EXE% test%O% 5 | 6 | test%O%: test.c 7 | %CC% -c %CCFLAGS% %CCOUT%test%O% test.c 8 | -------------------------------------------------------------------------------- /t/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | printf("Hello world!\n"); 6 | return 0; 7 | } 8 | --------------------------------------------------------------------------------