Skip to content
Snippets Groups Projects
build.clj 5.62 KiB
Newer Older
Cam Saul's avatar
Cam Saul committed
(ns build
  (:require [clojure.java.io :as io]
            [clojure.string :as str]
            [clojure.tools.build.api :as b]
            [clojure.tools.build.util.zip :as build.zip]
Cam Saul's avatar
Cam Saul committed
            [clojure.tools.namespace.dependency :as ns.deps]
            [clojure.tools.namespace.find :as ns.find]
            [clojure.tools.namespace.parse :as ns.parse]
            [hf.depstar.api :as depstar]
            [metabuild-common.core :as u])
Cam Saul's avatar
Cam Saul committed
  (:import java.io.OutputStream
           java.net.URI
           [java.nio.file Files FileSystems OpenOption StandardOpenOption]
           java.util.Collections
           java.util.jar.Manifest))

(def class-dir "target/classes")
(def uberjar-filename "target/uberjar/metabase.jar")

(defn do-with-duration-ms [thunk f]
  (let [start-time-ms (System/currentTimeMillis)
        result        (thunk)
        duration      (- (System/currentTimeMillis) start-time-ms)]
    (f duration)
    result))

(defmacro with-duration-ms [[duration-ms-binding] & body]
  (let [[butlast-forms last-form] ((juxt butlast last) body)]
    `(do-with-duration-ms
      (fn [] ~@butlast-forms)
      (fn [~duration-ms-binding]
        ~last-form))))

(defn create-basis [edition]
  {:pre [(#{:ee :oss} edition)]}
  (b/create-basis {:project "deps.edn", :aliases #{edition}}))

(defn all-paths [basis]
  (concat (:paths basis)
          (get-in basis [:classpath-args :extra-paths])))

(defn clean! []
  (u/step "Clean"
    (u/step (format "Delete %s" class-dir)
Cam Saul's avatar
Cam Saul committed
      (b/delete {:path class-dir}))
    (u/step (format "Delete %s" uberjar-filename)
Cam Saul's avatar
Cam Saul committed
      (b/delete {:path uberjar-filename}))))

;; this topo sort order stuff is required for stuff to work correctly... I copied it from my Cloverage PR
;; https://github.com/cloverage/cloverage/pull/303
(defn- dependencies-graph
  "Return a `clojure.tools.namespace` dependency graph of namespaces named by `ns-symbol`."
  [ns-decls]
  (reduce
   (fn [graph ns-decl]
     (let [ns-symbol (ns.parse/name-from-ns-decl ns-decl)]
       (reduce
        (fn [graph dep]
          (ns.deps/depend graph ns-symbol dep))
        graph
        (ns.parse/deps-from-ns-decl ns-decl))))
   (ns.deps/graph)
   ns-decls))

(defn metabase-namespaces-in-topo-order [basis]
  (let [ns-decls   (mapcat
                    (comp ns.find/find-ns-decls-in-dir io/file)
                    (all-paths basis))
        ns-symbols (set (map ns.parse/name-from-ns-decl ns-decls))]
    (->> (dependencies-graph ns-decls)
         ns.deps/topo-sort
dpsutton's avatar
dpsutton committed
         (filter ns-symbols)
         (cons 'metabase.bootstrap))))
Cam Saul's avatar
Cam Saul committed

(defn compile-sources! [basis]
Cam Saul's avatar
Cam Saul committed
    (let [paths    (all-paths basis)
          _        (u/announce "Compiling Clojure files in %s" (pr-str paths))
          ns-decls (u/step "Determine compilation order for Metabase files"
Cam Saul's avatar
Cam Saul committed
                     (metabase-namespaces-in-topo-order basis))]
      (with-duration-ms [duration-ms]
        (b/compile-clj {:basis      basis
                        :src-dirs   paths
                        :class-dir  class-dir
                        :ns-compile ns-decls})
        (u/announce "Finished compilation in %.1f seconds." (/ duration-ms 1000.0))))))
Cam Saul's avatar
Cam Saul committed

(defn copy-resources! [edition basis]
Cam Saul's avatar
Cam Saul committed
    ;; technically we don't NEED to copy the Clojure source files but it doesn't really hurt anything IMO.
    (doseq [path (all-paths basis)]
Cam Saul's avatar
Cam Saul committed
        (b/copy-dir {:target-dir class-dir, :src-dirs [path]})))))

(defn create-uberjar! [basis]
Cam Saul's avatar
Cam Saul committed
    (with-duration-ms [duration-ms]
      (depstar/uber {:class-dir class-dir
                     :uber-file uberjar-filename
                     :basis     basis})
      (u/announce "Created uberjar in %.1f seconds." (/ duration-ms 1000.0)))))
Cam Saul's avatar
Cam Saul committed

(def manifest-entries
  {"Manifest-Version" "1.0"
   "Multi-Release"    "true"
Cam Saul's avatar
Cam Saul committed
   "Created-By"       "Metabase build.clj"
   "Build-Jdk-Spec"   (System/getProperty "java.specification.version")
dpsutton's avatar
dpsutton committed
   "Main-Class"       "metabase.bootstrap"})
Cam Saul's avatar
Cam Saul committed

(defn manifest ^Manifest []
  (doto (Manifest.)
    (build.zip/fill-manifest! manifest-entries)))
Cam Saul's avatar
Cam Saul committed

(defn write-manifest! [^OutputStream os]
  (.write (manifest) os)
  (.flush os))

;; the customizations we need to make are not currently supported by tools.build -- see
;; https://ask.clojure.org/index.php/10827/ability-customize-manifest-created-clojure-tools-build-uber -- so we need
;; to do it by hand for the time being.
(defn update-manifest! []
Cam Saul's avatar
Cam Saul committed
    (with-open [fs (FileSystems/newFileSystem (URI. (str "jar:file:" (.getAbsolutePath (io/file "target/uberjar/metabase.jar"))))
                                              Collections/EMPTY_MAP)]
      (let [manifest-path (.getPath fs "META-INF" (into-array String ["MANIFEST.MF"]))]
        (with-open [os (Files/newOutputStream manifest-path (into-array OpenOption [StandardOpenOption/WRITE
                                                                                    StandardOpenOption/TRUNCATE_EXISTING]))]
          (write-manifest! os))))))

;; clojure -T:build uberjar :edition <edition>
(defn uberjar [{:keys [edition], :or {edition :oss}}]
  (u/step (format "Build %s uberjar" edition)
Cam Saul's avatar
Cam Saul committed
    (with-duration-ms [duration-ms]
      (clean!)
      (let [basis (create-basis edition)]
        (compile-sources! basis)
        (copy-resources! edition basis)
        (create-uberjar! basis)
        (update-manifest!))
      (u/announce "Built target/uberjar/metabase.jar in %.1f seconds."
Cam Saul's avatar
Cam Saul committed
                  (/ duration-ms 1000.0)))))

;; TODO -- add `jar` and `install` commands to install Metabase to the local Maven repo (?) could make it easier to
;; build 3rd-party drivers the old way