Proposal: Method to build NIFs using the Gleam build tool #3165
Replies: 4 comments 3 replies
-
I'm used to all of the referenced prior-art being the most painful parts of those ecosystems, either due to unreliable build scripts or due to them being abused to do other things. For the makefile approach is that suitably cross-platform? I don't know how popular and easy to use make is on Windows. Is it something Windows users have installed? How would Windows compilation work for the version where we run the C compiler directly? How would the non-make version work for compiling native deps beyond very simple C code, where some other compiler or more sophisticated build tool is needed. If we have make support it will need to not be automatically run if present. It needs to be opt in and directed in some fashion. |
Beta Was this translation helpful? Give feedback.
-
I think dagger.io is a tool that has a slightly different approach and is tackling some of these issues. i.e. reproducabile cross platform builds. I'm not yet sure how it handles cross platform. I'm experimenting to use it as a way to standardise all my CI and deploy scripts. |
Beta Was this translation helpful? Give feedback.
-
I've heard claims that |
Beta Was this translation helpful? Give feedback.
-
Another alternative is allowing the creation a build script written in Gleam. This would come with a new package to facilitate the compilation. Assuming the following file structure: hello //// build.gleam
import gleam/build as b
pub fn main() {
let assert Ok(_) =
b.new(name: "add_nif")
|> b.add_source("c_src/add_nif.c")
|> b.add_header("c_src/add.h")
|> b.add_library("libwobble")
|> b.build()
} Before I have created an example just to showcase the idea. https://github.com/Enderchief/gleam_nif_example/blob/master/hello/test/build.gleam |
Beta Was this translation helpful? Give feedback.
-
Proposal: Method to build NIFs using the Gleam build tool
Problem
Compiling NIFs for Gleam
Distributing a Gleam package that includes a NIF (Native Implemented Function) is not possible with the current Gleam build tool without distributing the precompiled shared libraries (SO/DLL) for every platform and choosing the correct one at runtime. This could lead to unnecessarily large packages due to the amount of outputs needed.
To build a C NIF from source, a c compiler has to be called from the command line. An example command using GCC would be
gcc -o "./priv/add_nif.so" -fpic -shared c_src/add_nif.c c_src/add.c
(This command runs on Linux with GCC and MacOS with a symbolic link to LLVM C Compiler). On Windows, it would becl -LD -MD -Fe "priv\add_nif.dll" "c_src\add_nif.c" "c_src\add.c"
.Current Solutions
Distributing all shared libraries in a package
Instead of compiling at Gleam build time, compile the NIF for each target in advance and include that in the package. Although it seems simple, it would mean having to compile at the minimum for: Linux x86, Linux ARM64, MacOS x86, MacOS ARM and Windows x86 (or more for 64-bit platforms, *BSD, etc.). Additionally, this would substantially increase the size of a project with unnecessary files.
Use an alternative build tool
Using Rebar3 (most common) or Erlang.mk. These allow providing commands to run before building the package. The problem with this is losing the standard Gleam tool.
Build during execution
When running Gleam, the package checks if the shared library has been compiled and if not, starts a subprocess to compile it.
Make the package user build the library
Either give instructions or provide a command (ex:
gleam run -m foo/build
) which builds during its execution. This would require explicitly building the package by a user and in some cases can be left unnoticed with a user wondering the cause of an error.Goals
Not all required but it would be nice to have all.
-lNAME
with gcc)cl
instead ofgcc
if the platform is Windows)Try to follow this quote
Other Languages and Systems
As a basis, provided is a list of how other tools/language would handle building a C library for FFI.
Erlang (Rebar3)
Pre-build and post-build hooks specified in the
rebar.config
Erlang (Erlang.mk)
Either evokes the default
make
target if a Makefile is detected or will be built when environment variables such asC_SRC_DIR
are defined.Elixir (Mix)
The
compilers
property inmix.exs
specifies functions that should be run on compiling with Mix.more info
Python (setuptools)
Specify library name and list of files in
setup.py
(the standard for building package for Python) and set environment variables for options to the C compiler.more info
Node.js (node-gyp)
Specify files and compiler options in a
binding.gypi
file. (alternatively, the Node.js ecosystem prefers building in advance and installing the correct binary post install)Go (cgo)
Specify compiler flags as comment directives at the root of a package.
Java (Maven + maven-compiler-plugin)
Specify compiler options in the
pom.xml
file.Options to Implement
Gleam invokes make if a Makefile exist (Primarily Suggested)
Many Erlang packages that have a NIF, build under the assumption the machine has
make
installed. For Linux and MacOS developers, most havemake
andgcc
on their system. For Windows developers, many use MinGW/Mingw-w64 which provide these tools.In
gleam.toml
an option is provided whether to call make or not on build. Whentrue
, Gleam during build will runmake
. Library developers can choose what to put within their Makefile.Gleam Runs the Compiler, You give the Options. (Alternative Suggestion)
In
gleam.toml
In non-Windows platforms this will run as
gcc -o "./priv/add_nif.so" -fpic -shared -I./include -lsomelibrary c_src/add.c c_src/add_nif.c
In Windows, Gleam will search for first GCC. If found, it will use the same command as above, replacing the POSIX paths to Windows paths (
gcc -o ".\priv\add_nif.dll" -fpic -shared -I.\include -lsomelibrary c_src\add.c c_src\add_nif.c c_src\win.c
).If not, it will throw a build error.
If not, it will fall back to using(forcing the use of gcc so that there is a common API and maintaining a build system that also uses MSVC would be tedious)cl
with the commandcl /LD /MD /Fe /I.\include "priv\add_nif.dll" "c_src\add_nif.c" "c_src\add.c" "c_src\win.c"
The only downside is inablilty to compile a non-C/C++ library. But doing so does require more work normally (although currently Rustler and zigler exist which make it easier). The upside is forcing people to go through one method leads to discouragement of "gnarly stuff".
Further Notes
Alternatively, I had considered (and started but stopped) writing about having pre/post build hooks (similar to Rebar3) but decided against it because it makes the method to building a NIF too open-ended rather than having one thing and keeping things consistent.
Looking at NIF based packages on Hex, I discovered many require Make and GCC, so it would be reasonable to assume someone using a NIF has those on their system.
Something important was to remember Windows developers. According to the Gleam 2022 Developer Survey, 14.15% of systems used are Windows for development (out of 318). As Gleam has grown so much in the past two months to people not normally familiar with BEAM, there are many more developers to consider.
I would like to thank the Gleam community for giving their requirements which I had searched through on Discord, also thanking the people behind all the tools I mentioned which served as an inspiration.
Beta Was this translation helpful? Give feedback.
All reactions