The 13th episode of Ivor the Engine (in colour) is titled Unidentified Objects. Let's hope our codebase don't have any of those.
- Idris the Dragon
Idris rules adds Idris support for Bazel. Bazel is a powerful and well maintained build tool with a lot of interesting characteristics. Combining Bazel and Idris rules, we get an idris build tool that:
- Can build different types of components (Executables, Libraries and tests)
- Make components easy to integrate between them
- It's easy to configure (for example, there is no need to specify the list of files/modules of your component by hand)
- Supports external dependencies. External dependencies only need to be Idris+Bazel projects hosted somewhere (like github, bitbucket, gitlab, ...). This means that, to support external dependencies there is no need to create and maintain some kind of central repository infrastructure. And it's easy for library developers to publish their work.
And there are more features to come
PREREQUISITES: Having bazel installed locally
Two quick options to get you started:
-
If you are already familiar with bazel, you can continue with the add rules_idris to a bazel project section
-
Otherwise head to the create a simple hello world tutorial
To get started with rules_idris, you only need to initialize them on your WORKSPACE
file and you will be ready to go. For that you have two options:
-
If you have the nix package manager installed locally, you can use it and bazel is going to get idris for you.
-
Otherwise, you can use a local installation of idris
Afterwards you might want to add an executable, a module or a test
PREREQUISITES: Having nix installed locally
This approach allows nix to retrieve idris for you. In fact, in the future this will allow per project configuration of the idris version to use.
Add the following to your WORKSPACE
file:
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "rules_idris",
remote = "https://github.com/BryghtWords/rules_idris.git",
tag = "v0.1"
)
load("@rules_idris//idris:idris_repos.bzl", "loadIdrisRepositories")
loadIdrisRepositories()
load("@rules_idris//idris:idris_loader.bzl", "loadIdris")
loadIdris()
PREREQUISITES: Having idris installed locally
With this approach, you need a local installation of idris, and to tell bazel where to find it.
- Add the following snippet into your
WORKSPACE
file
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "rules_idris",
remote = "https://github.com/BryghtWords/rules_idris.git",
tag = "v0.1"
)
load("@rules_idris//idris:idris_repos.bzl", "loadIdrisPackagerRepositories")
loadIdrisPackagerRepositories()
load("@rules_idris//idris:local_idris_loader.bzl", "loadIdris")
loadIdris("/path/to/idris/installation") # That is, wichever path that contains 'bin/idris'
- Locate where you have idris installed (that is, the folder that contains
bin/idris
) - On the text you have just added, replace
/path/to/idris/installation
with the correct path
Add the following into the BUILD
file for your executable:
load("@rules_idris//idris:rules_idris.bzl", "idris_binary")
idris_binary (
name = "name_of_the_binary",
)
It will automatically pick up all the idr
files from the same folder than the BUILD
file
Add the following into the BUILD
file for your executable:
load("@rules_idris//idris:rules_idris.bzl", "idris_library")
idris_library (
name = "name_of_the_library",
visibility = ["//visibility:public"],
)
It will automatically pick up all the idr
files from the same folder than the BUILD
file
If we want to test a library, like the one from the previous section, we should tweak it's BUILD
file to look like this:
load("@rules_idris//idris:rules_idris.bzl", "idris_library", "idris_test")
idris_library (
name = "library_example",
visibility = ["//visibility:public"],
)
idris_test (
name = "test_example",
deps = ["library_example"],
)
The idris_test
rule is going to pick up all the idr
files from the test
subfolder from where the BUILD
file is located. Each idr
in the test should have a test method with this signature:
export
test : IO ()
And the implementation of that method should do nothing if all the tests pass, or exit with a non zero value if the test fails. This will allow for easy integration with external testing libraries.
Creating a basic project for a simple executable, is a simple three step process:
- Create the project
- Setup idris rules
- Add the executable module
Bazel projects (also called workspaces) are collections of targets. For simplicity, I'm going to say that targets are things that can be built (this is not really true, but will help understand how bazel works).
In turn, this targets are organized into packages. All targets must belong to a package.
You can think of a workspace as a folder that contains a file named WORKSPACE
. Everything in that folder and subfolders belong to the workspace. Likewise, the WORKSPACE
file must live in the root folder of your project.
You can think of a package as a folder in the workspace that contains a file named BUILD
. Everything in that folder and subfolders that are not packages themselves (that is, that don't contain a BUILD
file) belong to the package.
If you look at this example:
|____my-package
| |____BUILD
| |____foo.h
| |____src
| | |____bar.cpp
| |____sub
| | |____BUILD
| | |____baz.h
| | |____src
| | | |____qux.cpp
my-package/foo.h
and my-package/src/bar.cpp
belong to the package my-package
, where my-package/sub/baz.h
and my-package/sub/src/qux.cpp
belong to sub
It's also worth noticing that the root folder can be both the workspace and a package. That is, by containing both a WORKSPACE
file and a BUILD
file.
The WORKSPACE
file holds general configuration for the project, things like what external repositories to access or tools to use. The BUILD
files list the targets for that package.
A bazel project is just a folder with a WORKSPACE
file. Here we go:
mkdir my-project
cd my-project
touch WORKSPACE
On this step you have to choose:
- To use the nix package manager (and install it if you don't already have it)
or
- To use a local installation of idris (and install it if you don't already have it)
After that, you basically need to add content on your WORKSPACE
file to tell bazel how to get and use rules_idris. It's preatty much a copy-and-paste as explained in here if you use nix or, otherwise as explained in here if you use a local installation of idris
This is where we create our first bit of idris. We will do three things:
- Create the package
- Create the idris code
- Configure the target
So first, as mentioned before, a package is a folder with a BUILD
file. Starting from the root folder:
mkdir bin # we can name this folder whatever we want
cd bin
touch BUILD
Then we need a bit of idris code. Let's put this code in bin/Binary.idr
:
module Main
main : IO ()
main = putStrLn "Hello, world!"
And finally, we need to tell bazel about it. Let's add this to the bin/BUILD
file:
load("@rules_idris//idris:rules_idris.bzl", "idris_binary")
idris_binary (
name = "binary_example",
)
And that's it, we can now build it:
bazel build //bin:binary_example
Or run it:
bazel run //bin:binary_example
After running either command, you can find your new file at:
bazel-bin/bin/bin/binary_example
- Testing integration needs to be improved:
- Each test component is a collection of Idris modules that contain a test function, if any of this test functions fail, the rest of test functions of that component will not get executed.
- Right now, the tests get executed in the bazel build fase. Idiomatic bazel requires that to happen in the run fase.
- Testing of the rules themselves need to be improved. In the examples foldere there is a prety big collection of bazel projects using rules_idris, with a different organisation each. And there is a 'test' script that builds and runs each of them in turn ensuring that everything goes well, but proper unit testing would be in order. And also integrate a CI so that we can have automatic checks that everything works.
- Improve testing integration
- Add support for starting the idris console from bazel
- Add support for the IDE mode on bazel projects
- Support multiple idris versions
- Add javascript and jvm rules
- Migrate the companion IdrisPackager project from Scala to Idris