Building multi-directory projects with jbuilder

I’ve been porting an OCaml project to jbuilder and having some confusion about how jbuilder resolves directories and dependencies.

In the top-level of my project I have a lib/ directory with its own jbuild to build it:

(jbuild_version 1)

(library
  ((name project)
   (public_name project)
   (libraries (various-things)
   (preprocess (pps (ppx_jane)))))

Also at the toplevel, I have a main.ml and a jbuild to build it:

(jbuild_version 1)

(executable
  ((name main)
   (libraries ( project ))))

From the top-level, I can run jbuild build @install to build everything inside lib and, jbuild build main.exe to build main.ml.

However, if I copy the main.exe from inside _build/default/ to the top-level (as I do via a Makefile), it seems like jbuilder does nothing.

I’m not sure why this is, or how to work around it.

I didn’t quite follow this bit – why are you moving main.exe to the toplevel build directory?

Just for convenience’s sake to run things. No deep reason.

So what actually goes wrong?
“it seems like jbuilder does nothing.”

What’s jbuilder supposed to do if you copy the build artefact somewhere else? :slight_smile:

One command that might be convenient is jbuilder exec, which executes a command with the build environment made available in the path. See jbuilder exec --help for more details.

So here is the relevant part of the Makefile, where I invoke jbuilder to build the exe file, and then move it into the top-level, renaming it to main.native.

build: lib/ main.ml
	jbuilder build main.exe
	cp _build/default/main.exe main.native

Let’s say I make a change to some file under lib/. With the above snippet, make will detect the change, invoke jbuilder which will compile a new main.exe, and then move it to the top-level as main.native. This is all as expected.

Now, let’s say I don’t rename main.exe to main.native (so there is a main.exe at the top-level), and make a change to some file under lib/. make still picks up the change and invokes jbuilder, but I don’t see any compilation happening, and no new main.exe is created.

So it seems like having a main.exe at the top-level (even if it’s a blank file I create with touch) will stop jbuilder from re-generating main.exe.

Makefiles cannot depend on directories in their dependencies, so your dependencies are incomplete. In this case, you can just create a PHONY target to force build to be recomputed on every invocation. Try:

.PHONY: build

build:
    jbuilder build main.exe
   cp _build/default/main.exe main.native

You could also make main.native a symlink.

Another alternative is to depend on the jbuilder logfile in _build/log to avoid a phony target.

I already had a line:

.PHONY: build test install uninstall clean

in the Makefile. In my case make was doing the right thing, and running the lines for build whenever things changed. The problem doesn’t seem to be with copying main.exe out of _build/defaults, but rather having any file named main.exe in the top-level.

I’m not able to reproduce this. I added a main.exe to the root of my project, and jbuilder still works as expected. In my test project, my executable is actually called something else, foo.exe, so I also tried adding foo.exe to the root, but jbuilder still works correctly. Perhaps you can provide a minimal example that exhibits the issue.

I’ve pushed a minimal example here: https://github.com/basus/jbuilder-bug

If you run jbuilder build main.exe, a main.exe binary is created as expected. After some investigation, it looks like what’s happening is that if there’s a main.exe in the top-level, then that file gets copied to _build/default when jbuilder is invoked, rather than being rebuilt.

This example is a bit different since your jbuild file is at the root of your project. In this case, jbuilder seems to be treating main.exe as a source file, which makes some sense since jbuilder considers the files in the directory in which a jbuild file resides. However, two things do seem wrong:

  • You also have a rule that builds a main.exe, so there are two ways to produce _build/default/main.exe. I suppose jbuilder should give an error.

  • I’m not sure why jbuilder is treating main.exe as a source file since I thought it only considers files with certain extensions like ml, mli, re, and rei.

I’m still learning jbuilder myself, so maybe I’m misunderstanding something.

Yes, i agree with you that it’s surprising that jbuilder thinks main.exe is a source file to be copied into _build rather than as a target to rebuild.

I’m not sure what you mean by saying there are now two ways to build main.exe. I thought I was required to have a jbuild file of the given form in the root directory in order to build an executable from a main.ml file there? I basically copied the example in the jbuilder quickstart. Without that jbuild file, i get an error on running jbuilder build main.exe:

Don't know how to build main.exe

The two ways being:

  • Your jbuild rule to generate main.exe from main.ml. The final location of this would be _build/default/main.exe.
  • The implicit rule (which I feel shouldn’t exist but it seems to be) that copies the source file main.exe also to _build/default/main.exe.

I didn’t mean you could avoid a jbuild file. You do certainly need that, but all your source files and jbuild file could be in a sub-directory src/. Then the above issue no longer exists. The first case would generate a file at _build/default/src/main.exe.

1 Like