Getting Started with Gerbil Development

This is a quick start guide to setting up your Gerbil development environment, starting from scratch.

Install Gerbil

Note

Older versions of Gerbil, prior to the v0.18 release cycle, required you to configure and install Gambit separately. This is no longer the case, as Gambit has been integrated as a git submodule.

Also note that there is no longer a need to set your GERBIL_HOME variable; in fact you should not set this unless you have special requirements.

  • Clone the repo from github

  • Install dependencies

The default configuration has some dependencies you may need to install: sqlite, zlib, and libcrypto/openssl You can install them in ubuntu with:

$ sudo apt install libssl-dev zlib1g-dev libsqlite3-dev
  • Configure Gerbil

I usually configure Gerbil for development with the following incantation:

./configure --prefix=/usr/local/gerbil

This will install Gerbil in /usr/local/gerbil; you should add /usr/local/gerbil/bin to your path.

Note that this configuration enables shared libraries: all gerbil programs will use shared libraries for libgambit and libgerbil linkage, resulting in significantly smaller executables.

If you are on Linux or BSD, I also recommend using --enable-poll, which will use the more scalable poll-based i/o scheduler (instead of the default select-based one).

Note

Do not use --enable-poll if you are on MacOS, as console polling is broken.

If you intend to build static executables for servers, then you should add --disable-shared to configure the system to use static libgerbil.a and libgambit.a libraries.

Also note, that it is strongly recommended to use gcc as your compiler, as LLVM-based compilers compile gerbil code very slowly (some times 10x slower than gcc) and also produce slower binary code.

You can configure the use of a specific compiler by configuring with the CC=compiler variable and setting the GERBIL_GCC environment variable to point to your preferred compiler.

If you are having problems with your system's ar, you can also set the GERBIL_AR environment variable to point to a specific ar that works.

Finally, the Gerbil Makefile honors the -j option; e.g. make -j4 to build using 4 cores.

If you want to enable standard library modules that are not built by default, you can do so by configuring with ./configure ... --enable-<feature> .... You can see what the default features are and aren't by using ./configure --help: it will offer you options that modify the defaults by enabling features that aren't enabled by default, or disabling features that are enabled by default.

Configure your Shell

To complete your installation, you have to configure your shell to find Gerbil, by editing e.g. your $HOME/.profile, $HOME/.bashrc or $HOME/.zshenv.

Whether you install Gerbil to some directory or not, be sure to add your $GERBIL_INSTALL_PREFIX/bin to your PATH so your shell and other programs can find gxi and gxc; alternatively, you could symlink the installed gerbil binaries into a directory already in your $PATH.

In addition, you should add your personal gerbil path's bin (by default ~/.gerbil but overridable with the GERBIL_PATH environment variable) into your path.

I (vyzo) have the following in my .bashrc:

add_path() {
    if [[ ! "$PATH" =~ $1 ]]; then
        export PATH="$PATH:$1"
    fi
}

GERBIL_INSTALL_PREFIX=/usr/local/gerbil # no need to export this
add_path $GERBIL_INSTALL_PREFIX/bin
add_path $HOME/.gerbil/bin

Prepare your Project

You can get started right away and write a script, but let's do a simple compiled library module and an executable as it is more relevant for real world development.

First create your namespace -- I recommend you use a top package for your libs so that you don't have conflicts with other packages.

The gerbil build tool uses your username by default.

So, let's start a simple project, which we will put into the hello-world directory:

$ mkdir hello-world
$ cd hello-world/

$ gerbil help new
Usage: gxpkg new [command-option ...]
       Create a new package template in the current directory

Command Options:
 -p --package <package>           The package prefix for your project; defaults to the current username [default: vyzo]
 -n --name <name>                 The package name; defaults to the current directory name [default: hello-world]
 -l --link <link>                 Optionally link this package with a public package name; for example: github.com/your-user/your-package [default: #f]

$ gerbil new -n hello

$ ls -latR
.:
total 28
drwxrwxr-x 3 vyzo vyzo 4096 Sep 24 09:52 .
-rwxr-xr-x 1 vyzo vyzo  138 Sep 24 09:52 build.ss
-rw-rw-r-- 1 vyzo vyzo   16 Sep 24 09:52 gerbil.pkg
-rw-rw-r-- 1 vyzo vyzo   27 Sep 24 09:52 .gitignore
drwxrwxr-x 2 vyzo vyzo 4096 Sep 24 09:52 hello
-rw-rw-r-- 1 vyzo vyzo  593 Sep 24 09:52 Makefile
drwxrwxr-x 8 vyzo vyzo 4096 Sep 24 09:52 ..

./hello:
total 16
drwxrwxr-x 2 vyzo vyzo 4096 Sep 24 09:52 .
drwxrwxr-x 3 vyzo vyzo 4096 Sep 24 09:52 ..
-rw-rw-r-- 1 vyzo vyzo  109 Sep 24 09:52 lib.ss
-rw-rw-r-- 1 vyzo vyzo  777 Sep 24 09:52 main.ss

$ cat gerbil.pkg
(package: vyzo)

$ cat build.ss
#!/usr/bin/env gxi
;;; -*- Gerbil -*-
(import :std/build-script)

(defbuild-script
  '("hello/lib"
    (exe: "hello/main" bin: "hello")))

At this point we have a new project template, which is ready to build and is waiting for you to fill your code.

The tool has created the following files in the current directory:

  • gerbil.pkg -- this includes project meta-data, the most important being the package namespace. As we can see, this is my vyzo user name in this example
  • build.ss -- this is the project build script, pre-filled with the two source files the tool generated.
  • Makefile -- this is the project release Makefile, which you can use to build release executables with docker.

It also created two source file templates, one for library code and one for the executable:

$ cat hello/lib.ss
;;; -*- Gerbil -*-
(import :std/sugar)
(export #t)

;;; Your library support code
;;; ...


$ cat hello/main.ss
;;; -*- Gerbil -*-
(import :std/sugar
        :std/cli/getopt
        ./lib)
(export main)

;; build manifest; generated during the build
;; defines version-manifest which you can use for exact versioning
(include "../manifest.ss")

(def (main . args)
  (call-with-getopt hello-main args
    program: "hello"
    help: "A one line description of your program"
    ;; commands/options/flags for your program; see :std/cli/getopt
    ;; ...
    ))

(def* hello-main
  ((opt)
   (hello-main/options opt))
  ((cmd opt)
   (hello-main/command cmd opt)))

;;; Implement this if your CLI doesn't have commands
(def (hello-main/options opt)
  (error "Implement me!"))

;;; Implement this if your CLI has commands
(def (hello-main/command cmd opt)
  (error "Implement me!"))

Next, let's build it:

$ gerbil help build
Usage: gxpkg build [command-option ...] <pkg> ...
       rebuild one or more packages and their dependents

Command Options:
 -R --release                     build released (static) executables
 -O --optimized                   build full program optimized executables

Arguments:
 pkg                              package to build; all for all packages, omit to build in current directory

$ gerbil build
... build in current directory
... compile hello/lib
... compile hello/main
... compile exe hello/main -> /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello
/tmp/gxc.1695538439.3642368/vyzo__hello__main.scm:
/home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello.scmx:
/tmp/gxc.1695538439.3642368/vyzo__hello__main.c:
/home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello.c:
/home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello_.c:

And we have an executable, which is placed by default in .gerbil/bin. You can change this by exporting the GERBIL_PATH variable.

Of course our executable doesn't do anything right now, as we haven't filled any code:

$ gerbil env hello
*** ERROR --
*** ERROR IN ? [Error]: Implement me!
--- continuation backtrace:
0  error

Notice that we ran the executable using gerbil env, which executes a command in the local package environment, with GERBIL_PATH set to .gerbil and GERBIL_PATH/bin prefixed to the PATH.

Write Some Code

We'll structure our (admittedly trivial) code in some library code and the executable's main logic.

In the generated hello/lib.ss file, we add our greeting procedure:

$ cat hello/lib.ss
;;; -*- Gerbil -*-
(import :std/sugar)
(export #t)

(def greeting "hello")
(def (greet who)
  (displayln greeting ", " who))

And in the generated hello/main.ss file, we add a getopt option for a single argument and implement hello-main/options to greet:

$ cat hello/main.ss
;;; -*- Gerbil -*-
(import :std/sugar
        :std/cli/getopt
        ./lib)
(export main)

(def (main . args)
  (call-with-getopt hello-main args
    program: "hello"
    help: "Greetings from my first Gerbil program"
    (argument 'who
      help: "your name or handle")))

(def* hello-main
  ((opt)
   (hello-main/options opt))
  ((cmd opt)
   (hello-main/command cmd opt)))

;;; Implement this if your CLI doesn't have commands
(def (hello-main/options opt)
  (greet (hash-ref opt 'who)))

;;; Implement this if your CLI has commands
(def (hello-main/command cmd opt)
  (error "Implement me!"))

So let's build it and run it:

$ gerbil build
... build in current directory
... compile hello/lib
... compile hello/main
... compile exe hello/main -> /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello
/tmp/gxc.1695538539.046348/vyzo__hello__lib.scm:
/tmp/gxc.1695538539.046348/vyzo__hello__main.scm:
/home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello.scmx:
/tmp/gxc.1695538539.046348/vyzo__hello__lib.c:
/tmp/gxc.1695538539.046348/vyzo__hello__main.c:
/home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello.c:
/home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello_.c:

$ gerbil env hello world
hello, world

More Build Options

Note that by default your program will be compiled with separate module compilation semantics and link to libgerbil. If you are willing to wait a bit for your program to compile, you can build your project with gerbil build --optimized or use the optimized-exe: build spec for your executable in the build script.

This will instruct the compiler to perform full program optimization.

For example:

$ ldd ./.gerbil/bin/hello
	linux-vdso.so.1 (0x00007ffe5f3b6000)
	libgerbil.so => /usr/local/gerbil/v0.17.0-294-g80c1d164/lib/libgerbil.so (0x00007fb29cc00000)
	libgambit.so => /usr/local/gerbil/v0.17.0-294-g80c1d164/lib/libgambit.so (0x00007fb29c200000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fb29eb30000)
	libssl.so.3 => /lib/x86_64-linux-gnu/libssl.so.3 (0x00007fb29ea8c000)
	libsqlite3.so.0 => /lib/x86_64-linux-gnu/libsqlite3.so.0 (0x00007fb29c0b3000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb29be00000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fb29eb6c000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb29cb19000)
	libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007fb29b800000)

$ gerbil clean
... clean current package
... remove /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/lib/vyzo/hello/lib.ssi
... remove /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/lib/static/vyzo__hello__lib.scm
... remove /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello
... remove /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/lib/static/vyzo__hello__main.scm

$ gerbil build --optimized
... build in current directory
... compile hello/lib
... compile hello/main
... compile exe hello/main -> /home/vyzo/src/vyzo/scratch/test/hello-world/.gerbil/bin/hello

$ ldd ./.gerbil/bin/hello
	linux-vdso.so.1 (0x00007fff585fc000)
	libgambit.so => /usr/local/gerbil/v0.17.0-294-g80c1d164/lib/libgambit.so (0x00007f6b2e600000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6b2e200000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6b2e502000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6b2efc9000)

If you want your program to be statically linked to dependent libraries, so that you can ship it as a release, you can specify the --release flag, which may be combined with --optimized.

Note that for release builds, your system must be configured with --disable-shared and have all foreign dependencies available as static library archives. Of course, you can do that by maintaining a separate gerbil build in your system for releases, but the recommended way to build release binaries is by using docker.

Using the Makefile

The generated Makefile has two main rules: the default linux-static rule which builds static executables for your project, and the utility clean rule to clean build artifacts.

So all you have to do to build a release executable is this:

$ make

This will build the release executable in .gerbil/bin in the current directory.

Dependency Management

Once you have started building more complex projects, you will naturally want to organize them into multiple packages. You are also likely to have some external dependencies to package developed by others.

The gerbil tool provides functionality to help with this situation.

Here are some examples:

  • Search for packages in the user configured directories (or just the default mighty-gerbils directory if none is configured):
# Search for packages
$ gerbil pkg search
github.com/mighty-gerbils/gerbil-crypto: Cryptography beyond OpenSSL
github.com/mighty-gerbils/gerbil-ethereum: Ethereum support
github.com/mighty-gerbils/gerbil-persist: Data persistence layer
github.com/mighty-gerbils/gerbil-leveldb: LevelDB bindings
github.com/mighty-gerbils/gerbil-libxml: libxml2 bindings
github.com/mighty-gerbils/gerbil-libyaml: Libyaml bindings
github.com/mighty-gerbils/gerbil-lmdb: LMDB bindings
github.com/mighty-gerbils/gerbil-mysql: MySQL database driver
github.com/mighty-gerbils/gerbil-poo: Prototype Object Orientation system
github.com/mighty-gerbils/gerbil-utils: Various utilities

# Search with keywords
$ gerbil pkg search xml
github.com/mighty-gerbils/gerbil-libxml: libxml2 bindings
  • Add dependencies to your project:
$ gerbil deps -a -i github.com/mighty-gerbils/gerbil-libxml
... cloning github.com/mighty-gerbils/gerbil-libxml
... pulling
... build github.com/mighty-gerbils/gerbil-libxml
... compile foreign xml/_libxml
... copy ssi xml/_libxml
... compile loader xml/_libxml
... compile xml/libxml
... tagging packages
  • List your project's dependencies:
$ gerbil deps
github.com/mighty-gerbils/gerbil-libxml

Where to go from here

You can find more information about packages in the Gerbil Package Manager page.

You can find more information about the gerbil tooling in the Universal Gerbil Binary and Tools page.

You can find more information about the build tool specifics in the Gerbil Build Tool page.