Spin With Luv, the Spin Build
Tags: reason, ocaml, spin
Published: 2020-04-19 01:44:39

---
title: Spin With Luv, the Spin Build
published: true
description: Debugging the build system
tags: reason, ocaml, spin, 
series: ReasonML, Dune, Pesy and Spin
---

In the previous post we built the project using `esy/pesy` only. Check it out [here](https://dev.to/idkjs/pesy-with-luv-the-esy-pesy-build-4a8e).

To recap, I am trying to use the [aantron/luv](https://github.com/aantron/luv) library to try to build something and I ran into all kinds of build system issues mostly because I still haven't grokked the reason-native build system yet. That's ok. This note is my way of getting better and having a reference I understand when I need to come back to it and giving back to the generous community. The `reason-native` build environment has a list of great tools you can use. Most prominent among them are [`Dune`](https://dune.build), [`Esy/Pesy`](https://github.com/esy/pesy) and [`Spin`](https://github.com/tmattio/spin). Here we are using `Spin`.

# Getting Started/Scaffolding with Spin

You can install `spin` every which way you can think of. `homebrew`,`bash`,`opam`, `npm`, `source`,`curl` whatever. Go to the readme to pick one.

I used `brew install tmattio/tap/spin`.

Running `spin new native luv-cli` creates a new directory from where ever you are in the file system then give you a bunch of prompts. This really feels like professional grade tooling.

I answered `1` to all the questions to keep this `ReasonML` all the way through.

```sh
❯ pwd
~/Github
❯ spin new native luv-cli
📡  Updating official templates.
Done!

Project name: luv-cli
Project slug: [luv-cli]
Description: [A short, but powerful statement about your project] luv wrapped in a cli
Which syntax do you use?
1 - Reason
2 - OCaml
Choose from (1, 2): 1
Which package manager do you use?
1 - Esy
2 - Opam
Choose from (1, 2): [1] 1
Which test framework do you prefer?
1 - Rely
2 - Alcotest
Choose from (1, 2): [1] 1
Which CI/CD platform do you use
1 - Github
2 - None
Choose from (1, 2): [1] 1
> cd luv-cli
```

This will produce the following directory structure:

```
> tree -L 2
.
├── CHANGES.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── _esy
│   └── default
├── bin
│   ├── dune
│   └── luv_cli_app.re
├── dune-project
├── esy.json
├── esy.lock
│   ├── index.json
│   ├── opam
│   └── overrides
├── lib
│   ├── dune
│   ├── utils.re
│   └── utils.rei
├── luv-cli.opam
├── luv-cli.opam.template
├── node_modules
└── test
    ├── _snapshots
    ├── dune
    ├── support
    └── utils_test.re

11 directories, 16 files

```

## Test It

Run `esy start` or `esy x luv-cli.exe`

```sh
❯ esy start
Hello World!
```

## Adding Color

If you haven't noticed yet, I'm basically following [Micheal Kohl's](https://twitter.com/@citizen428) post [native-cli-apps-in-reasonml-part-1](https://citizen428.net/blog/native-cli-apps-in-reasonml-part-1/) to figure out how to add dependencies to a spin project. In the `pesy-with-luv` build adding the `luv` dependency worked out well. But I can't figure out how to add it to a spin project with getting build errors. Since Kohl, is a contributor, I am figuring he will show us, so let's see.

Let add the dependencies we will need:

```sh
esy add @reason-native/console
esy add @reason-native/pastel
```

Next we have to add them to the Dune configuration file in `lib/dune`. In this file we’ll change the `libraries` stanza from
```
`(libraries base)`
```
to
```
`(libraries base console.lib pastel.lib)`
```
Note how this is diffent than when we used `pesy`. `pesy` asks us to add our new deps to `package.json` and rebuilds our `dune` file for us.

We can use our new tools in a new `Utils.hello` function. Update `utils.re` with:

```re
open Pastel;

let hello = () =>
  <Pastel bold=true color=Green>
    "Hello, "
    <Pastel italic=true color=Red> "World!" </Pastel>
    " 👋"
  </Pastel>;
```

We also have to had this function to `utils.rei` as you will have noticed if you tried to run `esy start`.

Another `pesy/spin` difference is that you can run your the project as soon as you make the changes. With `pesy` we had to run `esy pesy` then `esy` then our run command.

With `spin` you just run `esy start`. I noticed that it did not work the first time you run `esy start` but runs the second time without making any changes. Feels like the build system is lagging maybe? I have no idea.

Running `esy start` will now get you:
```sh
~/Github/luv-cli
❯ esy start
Hello World!
Hello, World! 👋
```

## Adding a Test

`spin` generated a test file for us when we ran the spin cli.

```re
open Test_framework;
open Luv_cli;

/** Test suite for the Utils module. */

let test_hello_with_name = (name, {expect}) => {
  let greeting = Utils.greet(name);
  let expected = "Hello " ++ name ++ "!";
  expect.string(greeting).toEqual(expected);
};

describe("Utils", ({test, _}) => {
  test("can greet Tom", test_hello_with_name("Tom"));
  test("can greet John", test_hello_with_name("John"));
});

```

Let's add another one for our new function:

```re
open Luv_cli;

/** Test suite for the Utils module. */
describe("Utils", ({test, _}) =>
  test("Utils.hello() returns a greeting", ({expect}) => {
    expect.string(Utils.hello()).toMatch("👋")
  })
);
```

Running `esy test` gets us 3 passing tests including our `Utils.hello` test.

Again, all we had to do was run `esy test`. We didn't have to rebuild. I'm finding that super convenient.

Here is what we got:

```sh
~/Github/luv-cli
❯ esy test
Running 2 test suites
 PASS  Utils
 PASS  Utils
```

## Adding Argument Parsing for Practice

Run `esy add @opam/cmdliner` the add `cmdliner` to `lib/dune` like we did before.

Our `libraries` stanza now looks like this:

```
(libraries base console.lib pastel.lib cmdliner)
```

`stanza`, by the way, as used in poetry, is what dune calls each line defining some behavior in project build. [wikipedia](https://en.wikipedia.org/wiki/Stanza). Appropriate use of the word, I think.

So now that we have [`Commandliner`](https://erratique.ch/software/cmdliner) let's use it in `bin/luv_cli_app`.

Add the following to `bin/luv_cli_app`:

```re
open Cmdliner;

let cmd = {
  let doc = "Simple CLI for Luvers built in Reason";

  let who = {
    let doc = "Who do you want to greet";
    Arg.(
      required
      & pos(0, some(string), None)
      & info([], ~docv="WHO", ~doc)
    );
  };

  let run = who => {
    Console.log @@ Utils.helloInput(who);
  };

  Term.(const(run) $ who, info("luv-cli", ~doc));
};

let () = Term.exit @@ Term.eval(cmd);
```

Notice I have `cmd` calling `Utils.helloInput`. I wanted to keep the other functions as they are to compare so lets add another to `utils.re` and `utils.rei`.

Here we change the previous `Utils.hello` so that it takes a string, and rename it `Utils.helloInput`

```re
let helloInput = (string) =>
  <Pastel bold=true color=Green>
    "Hello, "
    <Pastel italic=true color=Red> string </Pastel>
    " 👋"
  </Pastel>;
```

Then add `let helloInput: string => string;` to `utils.rei`.

Run `esy start Luvers` where `Luvers` is just the string the command expects.

```sh
❯ esy start Luvers
Hello World!
Hello, World! 👋
Hello, Luvers 👋
```

What happened here? We used `Open Cmdliner` in `bin/luv_cli_app` but we didnt add it to `bin/dune`. We only added it to `lib/dune` and we did not even use it there. So I as a matter of structuring our project, we are including everything we need in our `lib` and calling any library functions we might want to use from our `lib`. Just for fun, take `cmdliner` out of `lib/dune`, add it to `bin/dune` then run `esy start`. You should get the same result. The way [Kohl](https://twitter.com/@citizen428) did it make sense though. It keeps control of everything in one place. After all, at this point, the only time we use `cmdliner` is when calling it with from `lib/utils.re`. This makes sense to me.


## Adding Luv, First Attempt

`esy add luv`, adding to `lib/dune`:
```
(libraries base console.lib pastel.lib cmdliner luv)
```
then `esy start` gets:

```sh
❯ esy start Luvers
File "lib/dune", line 5, characters 48-51:
5 | (libraries base console.lib pastel.lib cmdliner luv)
                                                    ^^^
Error: Library "luv" not found.
Hint: try:
dune external-lib-deps --missing --root . --only-package luv-cli @@default
error: command failed: 'refmterr' 'dune' 'build' '--root' '.' '--only-package' 'luv-cli' (exited with 1)
esy-build-package: exiting with errors above...
error: build failed with exit code: 1
  
```

This got the best of me. I had to file an issue with the `spin` repository. I will report back when I have figured this out.

## Update 1

[@tmattio](https://tmattio.github.io) got back to us [here](https://github.com/tmattio/spin/issues/73#issuecomment-616199171). Thanks Thibault!

This is what he had to say.

> I believe the issue is that Result.get_ok comes from the standard library, but Spin-generated projects use Base. We took the decision to use the standard library instead of Base recently (see tmattio/spin-templates#10), but I didn't release a version with this change yet.

For now, you can either remove Base (by removing it from dune-project and every dune file), or you can use the equivalent of Result.get_ok in Base, which should be Result.return.

Seems like a simple answer, but really, there is a lot to unpack.

First thing I will do is try his suggestion to use the included `Base.Result` type and see what happens.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yn047cyuprpjcd62nzx3.png)

Doesn't work, not without doing some other refactoring, anyway. 
Committed to [learning in public](https://www.swyx.io/writing/learn-in-public/) to share the knowledge. `Luv` is using the `StdLib.return` type so I am not going to chase this down right now but it would be a good learning exercise to switch out the types. So what is `Base`. It's another library of standard utility functions created and battle tested at [JaneStreet](https://github.com/janestreet/base), the largest user of OCaml in production out there. Reading through the [blog](https://blog.janestreet.com) is recommended! I have this `ocaml`[search](https://blog.janestreet.com/search/?query=ocaml) bookmarked.

The spin project has included `Base` library and made it available globally. If we go to our project repo and open any `dune` file we will see something like this:

```
(library
 (name Luv_cli)
 (public_name luv-cli.lib)
 (modules (:standard))
(libraries base console.lib pastel.lib cmdliner)
 (flags -open Base))


(include_subdirs unqualified)
```

See the `(flags -open Base)` stanza? That is syntax for making `Base` available globally in `/library` or anywhere else you pass the flag. If we remove it, we should be on our way. I searched the project and removed and `base` or `-open Base` references, deleted the `_esy` and `esy.lock` directories and get some dune:the movie dialog references! Cool but not a solution.

Stand by for further updates. 

# Update 2 and Solution

The library maintainer, TMattio, chased this thing down. Apparently, the standard workflow was not working because a possible bug in `esy`. You can see the discussion, here: [https://github.com/tmattio/spin/issues/73#issuecomment-617435857]. He aslo provided an alternative workflow to use in the meantime which is to build with `esy` and `dune` like we did in the [Pesy-with-luv](https://dev.to/idkjs/pesy-with-luv-the-esy-pesy-build-4a8e) version. Remember that all these builds are using `dune` and we still have access to `dune` whichever we are using. If we build the spin project, [luv-cli](https://github.com/idkjs/luv-cli) with `esy dune build` the run with `esy start` we get:

```
❯ esy dune build
                     
~/Github/luv-cli adding-luv*
❯ esy start
Hello World!
Hello, World! 👋
luv-cli: required argument WHO is missing
Usage: luv-cli [OPTION]... WHO
Try `luv-cli --help' for more information.

~/Github/luv-cli adding-luv*
❯ 
``` 

# Conclusions

1. [TMattio](https://tmattio.github.io/) is an engaged library maintainer.
2. We helped find a bug in the project by being engaged.
3. [Spin](https://github.com/tmattio/spin) is a very nice alternative and will likely only get better.

Thanks for learning with me. Be sure to checkout [`Dune`](https://dune.build), [`Esy/Pesy`](https://github.com/esy/pesy) and [`Spin`](https://github.com/tmattio/spin) for your next projects.

Peace and love to you all.

Committed to [learning in public](https://www.swyx.io/writing/learn-in-public/) to share the knowledge.