Welcome! Please see the About page for a little more info on how this works.

0 votes
in tools.build by

In the context of a monorepo containing several modules I have noticed an issue with clojure.tools.build.api/compile-clj which is making difficult to compile files when the basis project root is not in the current directory.

Here is a minimal scenario demonstrating the problem.

I have a top-level monorepo deps.edn using tools.build to prepare/package its modules

 {:deps {monorepo/module-a {:local/root "modules/a"}}
 :aliases {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.2"
                                                         :git/sha "ba1a2bf"}}
                   :ns-default build}}}

Module A contains some clojure source files. Here is modules/a/deps.edn

{:paths ["src"]}

Those files require a compilation step. Here is the top-level build.clj file expected to proccess those files.

(ns build
  (:require [clojure.tools.build.api :as b]))

(def module-a (binding [b/*project-root* "modules/a"]
                (b/create-basis {:project "deps.edn"})))

(defn compile-a [_]
   {:basis module-a
    :src-dirs ["modules/a/src"]
    :class-dir "target/module-a/classes"}))

However when invoking clojure -T:build compile-a from top-level directory I encounter the following error

Execution error (FileNotFoundException) at user/eval136$fn (compile.clj:5).
Could not locate monorepo/module/a__init.class, monorepo/module/a.clj or monorepo/module/a.cljc on classpath.

The problem is that the "src" relative directory is not expanded in the :classpath-roots which lead compile-clj to search source files in the wrong location. It is possible to work around the issue by binding the *project-root* again and adapting :src-dirs and class-dir relative to module A root directory like this

(defn compile-a [_]
  (binding [b/*project-root* "modules/a"]
     {:basis module-a
      :src-dirs ["src"]
      :class-dir "../../target/module-a/classes"})))

However IMO it would be better if the computed classpath in the basis was fully expanded by transforming relative paths into absolute ones to avoid this extra ceremony.

What do you think?
Is there a reason why relative source path directories are currently kept relative ?

1 Answer

0 votes

You should be binding *project-root so that relative paths are correctly resolved for both relative paths in the project and for any transitive local deps. The project-root* is there for this purpose. I think it's better not to be putting the basis in a var but instead to bind outside any calls to the build tasks (like create-basis and compile-clj).

Yes I understand that *tools.build* currently requires binding `*project-root*` when calling any build task that depend on path resolution.

What I am trying to say is that this is making things a bit inconvenient in the context of a monorepo where you can manipulate multiple module basis referencing `deps.edn` files located in different directories that you would want to process uniformly.

A proposition that could simplify things would be to relax the requirement of binding `*project-root*` and make it a static property of the basis corresponding to the base directory of the `deps.edn` file used to construct the basis.

    (binding [b/*project-root* "modules/a"]
                (b/create-basis {:project "deps.edn"}))

could then simply be replaced by

    (b/create-basis {:project "modules/a/deps.edn"})

and invoking `compile-clj` would then not require setting the project root anymore because the basis that is passed as a parameter would already know it. To implement that design, making `deps.edn` relative paths absolute according to the `deps.edn` base directory would do the job because it allows extra relative paths parameters to still be resolved according to `build.clj` base directory.

Does it make sense ?
It does. I'll think about it.