понедельник, 6 ноября 2017 г.

clojure monorepo how-to

Monorepo is the way to manage several related projects from one point. In this note I'll show you how to organize monorepo using Amperity plugin lein-monolith .

0. Lets create root project and some subprojects
  rootproject
                    project.clj        ;;project file for root project
                    /apps
                            /app01     ;; project folder for app01 project
                    /libs
                            /lib01       ;; project folder for lib01 project which is needed for app01
                            /lib02       ;; some other lib


1. We should put section :plugins [[lein-monolith "1.0.1"]] to root project.clj and to project.clj of all subprojects.

(defproject rootproject/all "MONOLITH"
  :description "Mono repo"

  :plugins [[lein-monolith "1.0.1"]
                 [lein-cprint "1.3.0"]]

;; some repo which is used in all subprojects
  :repositories {"readytalk" {:url "https://dl.bintray.com/readytalk/maven/"}}

 ;; this is local deps that belongs only root project until section :dependencies is included in :inherit section. But for :inherit section we will use :managed-dependencies instead.
  :dependencies [[org.clojure/clojure "1.9.0-beta4"]]

;; this managed-deps will be delivered to all subprojects
  :managed-dependencies [[org.clojure/clojure "1.9.0-beta4"]]
 
  :monolith {
             ;; here we tell lein-monolith to merge :repositories and :managed-dependencies
             ;; to all subprojects.
             :inherit [:repositories :managed-dependencies]

            ;; here we tell that section profiles from root projects should be merged to all subprojects.
            ;; in this example we tell that in uberjar we should omit source files.
             :inherit-leaky [:profiles]

             ;; where is our subprojects located
             :project-dirs ["apps/*" "libs/*"]}

  :profiles {:uberjar {:omit-source true}}

  :env {:foo "bar"})

2. Every project.clj of all subprojects we prepare like this:

(defproject rootproject/app01 "2.0.0-SNAPSHOT"
  :description "FIXME: write description"

;; here we include lein-monolith for subproject
  :plugins [[lein-monolith "1.0.1"]
                 [lein-cprint "1.3.0"]]

 ;; here we tell that we need to inherit all sections from root project
  :monolith/inherit true
  :deployable true

  :dependencies [[rootproject/lib01 "0.1.0-SNAPSHOT"]]
  :main ^:skip-aot lib01.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

3. Now we can run commands from root project
lein monolith each do clean, uberjar 
This command will clean and make uberjars for all subprojects. Remember, that we should run
lein install
for lib01 to install to a local repo for app01 which use it. Also we can run command only for particular subproject
lein monolith each :start rootproject/app01 do uberjar 
 This command will run uberjar target only for particular project.

Interesting fact that we can point out clojure version only in root project - project.clj, in section :managed-dependencies and this version of clojure will be delivered to all subprojects. So we don't need to include clojure version in subprojects.

Also from root project we can run different tests for different subproject using selectors. First of all wee should put metadata to all deftest like here
https://github.com/technomancy/leiningen/blob/2.4.3/src/leiningen/test.clj#L164

In example above it is integrations test with ^:integration metadata. Then we should put section
:test-selectors
{:unit (complement :integration)
:integration :integration}

in root project.clj and put :test-selectors to :inherit section.