bsb-native
Bsb-native is a fork of bsb that compiles to native OCaml instead.
Install
- Add
"bsb-native": "4.0.6"
as a devDependency to your package.json
- Add a
bsconfig.json
like you would for bsb. Bsb-native uses the same schema, located here with small additions like entries
(see below for complete config schema). - run
npm install
/ yarn
.
An example bsconfig.json:
{
"name" : "NameOfLibrary",
"sources" : "src",
"entries": [{
"backend": "bytecode",
"main-module": "Index"
}]
}
Run
./node_modules/.bin/bsb -make-world
(or add an npm script that runs bsb -make-world
).
That will build the first entry and use its backend
to build all other entries targetting that backend
.
e.g if you have multiple bytecode
targets, they'll all get built but not the js
ones nor native
ones. If you want to build to all targets you need to run the build command multiple times with different -backend
.
Initialize a new package
Bsb-native comes with a basic init package to get you started. To create a package named Hello
run:
bsb -init Hello
And a folder named Hello
will be created with a basic project layout. If you want to initialize an already created folder, use .
as the last argument.
Useful commandline flags
The -make-world
flag builds all of the dependencies and the project.
The -clean-world
flag cleans all of the build artifacts.
The -w
enabled the watch mode which will rebuild on any source file change.
The -backend [js|bytecode|native]
flag tells bsb-native
to build all entries in the bsconfig.json
which match the backend js
, bytecode
or native
.
The build artifacts are put into the folder lib/bs
. The bytecode executable would be at lib/bs/bytecode/index.byte
and the native one at lib/bs/native/index.native
for example.
The -build-library
flag takes a module name and will pack everything in the current project that depends on it into a library file. Cool trick: you can use this to implement hot reloading for any sort of app, simply call bsb-native with this flag and use the built-in module Dynlink
to load the file bsb-native generates.
Opam package support
Yes bsb-native
supports opam packages (see ocamlfind example).
BUT you need to be on the switch 4.02.3+buckle-master
(which you can get to by running opam switch 4.02.3+buckle-master
).
{
"name" : "NameOfLibrary",
"sources" : "src",
"ocamlfind-dependencies": ["lwt.unix", "lwt.ppx"],
"entries": [{
"backend": "bytecode",
"main-module": "Index"
}]
}
bsconfig.json schema
{
"sources": [{
"type": "ppx"
"ppx": ["Myppx", "theirPpx/theirPpx.exe"]
}]
"entries": [{
"backend": "bytecode",
"main-module": "MainModule",
"output-name": "snake.exe",
}],
"ocamlfind-dependencies": ["lwt.unix"],
"ocaml-dependencies": ["bigarray", "unix", "threads", "compiler-libs"],
"ocaml-flags": ["-bin-annot"],
"ocaml-linker-flags": ["-output-obj"],
"allowed-build-kinds": "js",
"static-libraries": ["lib/c/my_lib.o"],
"build-script": "build_script.re",
"c-linker-flags": ["-L/path/to/folder/with/linker/stuff"],
}
C compilation
bsb-native allows C compilation through an OCaml/Reason file. To expose that file to bsb you can add "build-script": "build_script.re"
to your bsconfig.json
.
Bsb expose to that file a module called Bsb_internals
which contains helpers to compile C code.
/* Command that'll synchronously compile a C file to an object file (or not if `c=false` is passed).
c:bool Equivalent to the `-c` flag passed to gcc. true by default.
include_ocaml:bool Automatically add an include flag for the ocaml runtime. true by default.
flags:array(string) Array of flags passed directly to gcc.
includes:array(string) Array of paths to folders that will be included during compilation.
string The output file.
array(string) The input files.
*/
let gcc : (?c:bool, ?include_ocaml:bool, ?flags:array(string), ?includes:array(string), string, array(string)) => unit;
/* Contains an absolute path to the current project's node_modules. */
let node_modules : string;
Here is a simple example of a build_script.re
file.
The generated artifacts need to be added to the "static-libraries": []
array inside the bsconfig.json
in order to be added at the linking phase.
Here's the same simple example's bsconfig.json
.
The gcc
function exposed to the build-script
file uses a vendored mingw compiler, which works on all platforms (linux, osx, windows).
Multi-target
bsb-native
actually supports building Reason/OCaml to JS as well as to native/bytecode. What that enables is cross platform code that depends only on an interface, and bsb-native will choose the right implementation depending on what you target.
For example, you can write code that depends on the module Reasongl, and bsb-native will use ReasonglNative
as the implementation for Reasongl when building to native/bytecode or it'll use ReasonglWeb
when building to JS.
Currently the way this works is to have each platform specific dependency expose a module with the same name and rely on the field allowed-build-kinds
in the bsconfig.json
to tell bsb-native which one of platform specific dep you want to build, given that you want to build the whole project to a specific target. Say you target JS, then ReasonglWeb will get built which exposes its own Reasongl module.
Conditional compilation
If you would like to have all your code in the same package, you can use BuckleScript's conditional compilation. To do so, place
#if BSB_BACKEND = "bytecode" then
include MyModule_Native
#elif BSB_BACKEND = "native" then
include MyModule_Native
#else
include MyModule_Js
#end
(* We support Darwin, Linux, Windows compile time checks *)
#if OS_TYPE = "Darwin" then
external fabs : float -> float = "fabs"
#end
inside a file called MyModule
(for example). When you build to JavaScript (BSB_BACKEND = "js"
), that module will use the MyModule_Js
implementation (see example). Same for BSB_BACKEND = "native"
and BSB_BACKEND = "bytecode"
.
BSB_BACKEND
value will be filled automatically by bsb-native
, so you just need to use it at will with the language-level static if
compilation.
Same for OS_TYPE
.
Platform specific files (like MyModule_Native
) should be added to a folder that is only built for that specific backend (native
, in the MyModule_Native
case). You can do that by adding this to your bsconfig.json
file:
"sources": [{
"dir": "src",
"subdirs": [{
"dir": "native",
"backend": "native"
}]
}]
Note: BuckleScript's conditional compilation doesn't work with Reason yet, so any usage of conditional compilation will have to be implemented in OCaml .ml
files.
PPX support
We support running ppxes on the whole codebase, like bucklescript with "ppx-flags"
, but also per "source"
directory. You can add ppx: ["my_ppx/my_ppx.exe"]
to any "source"
directory to run that ppx on the files inside that directory.
You can also make your own ppx inside you project. To keep things contained, you have to put the code in its own folder which you'll list under "sources"
with the extra key "type": "ppx"
. Then add an entry like you would for building a binary, to bytecode specifically, and similarly add to the entry "type": "ppx"
. Finally add to the "sources"
that contain the files on which to run that ppx "ppx": ["Myppx"]
(notice it's just a module name, that tells bsb-native it's a local ppx).
tl;dr
- add folder to
"sources"
with "type": "ppx"
- add entry built to bytecode with
"type": "ppx"
- add
"ppx"
field to right "sources"
object for files that use that ppx "ppx": ["Myppx"]
- (optional) bsb-native passes to the ppx a command line argument called
-bsb-backend
followed by the current backend, which is either "bytecode"
, "native"
or "js"
.
Also bsb-native comes with ppx_tools available by default, no config!. Here are some "docs".
You can't build ppxes to bytecode or js for now. You also can't have a ppx depend on another ppx. You can't put the ppx code alongside your app code. The ppx will always be built even if you never reference it.