From 542e6fbe18497e755cff7c15b549baf7a5b9881a Mon Sep 17 00:00:00 2001 From: Kyle Cesare Date: Wed, 7 Oct 2026 23:01:35 -0600 Subject: [PATCH 1/4] Add jank-build-util package --- jank-build-util/README.md | 10 ++++ jank-build-util/project.clj | 7 +++ jank-build-util/src/jank/build/util.clj | 48 +++++++++++++++++++ jank-build-util/test/jank/build/util_test.clj | 36 ++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 jank-build-util/README.md create mode 100644 jank-build-util/project.clj create mode 100644 jank-build-util/src/jank/build/util.clj create mode 100644 jank-build-util/test/jank/build/util_test.clj diff --git a/jank-build-util/README.md b/jank-build-util/README.md new file mode 100644 index 0000000..273b1ca --- /dev/null +++ b/jank-build-util/README.md @@ -0,0 +1,10 @@ +# jank-build-util +[![Clojars Project](https://img.shields.io/clojars/v/org.jank-lang.commons/jank-build-util.svg)] +Build script helper functions. + +## Usage +Add this project to your Leiningen `:build-dependencies` vector: + +```clojure +[org.jank-lang.commons/jank-build-util "0.1-SNAPSHOT"] +``` diff --git a/jank-build-util/project.clj b/jank-build-util/project.clj new file mode 100644 index 0000000..c285768 --- /dev/null +++ b/jank-build-util/project.clj @@ -0,0 +1,7 @@ +(defproject org.jank-lang.commons/jank-build-util "0.1-SNAPSHOT" + :description "Build script helper functions" + :url "https://github.com/jank-lang/commons" + :license {:name "MPL 2.0" + :url "https://www.mozilla.org/en-US/MPL/2.0/"} + :source-paths ["src" "test"] + :profiles {:dev {:dependencies [[org.clojure/clojure "1.12.0"]]}}) diff --git a/jank-build-util/src/jank/build/util.clj b/jank-build-util/src/jank/build/util.clj new file mode 100644 index 0000000..0b5590c --- /dev/null +++ b/jank-build-util/src/jank/build/util.clj @@ -0,0 +1,48 @@ +(ns jank.build.util + (:require [clojure.string :as string])) + +;; OS identification functions are adapted from the Apache Commons +;; Lang SystemUtils class. +;; +;; https://github.com/apache/commons-lang/blob/32dc4c501e3acba29d53a466eebd611bb2f093a5/src/main/java/org/apache/commons/lang3/SystemUtils.java + +(def os-name (System/getProperty "os.name")) + +(defn linux? [] + (-> (string/lower-case os-name) + (string/starts-with? "linux"))) + +(defn mac? [] + (-> (string/lower-case os-name) + (string/starts-with? "mac"))) + +(defn windows? [] + (-> (string/lower-case os-name) + (string/starts-with? "windows"))) + +;; Emit common "jank-build::"-prefixed items. + +(defn define + ([k] (println (str "jank-build::define=" k))) + ([k v] (println (str "jank-build::define=" k "=" v)))) + +(defn include-dir [dir] + (println (str "jank-build::include-dir=" dir))) + +(defn link-dir [dir] + (println (str "jank-build::link-dir=" dir))) + +(defn link-library [lib] + (println (str "jank-build::link-library=" lib))) + +(defn link-static-library [lib] + (println (str "jank-build::link-static-library=" lib))) + +(defn link-framework [framework] + (println (str "jank-build::link-framework=" framework))) + +(defn rerun-if-changed [path] + (println (str "jank-build::rerun-if-changed=" path))) + +(defn rerun-if-env-changed [k] + (println (str "jank-build::rerun-if-env-changed=" k))) diff --git a/jank-build-util/test/jank/build/util_test.clj b/jank-build-util/test/jank/build/util_test.clj new file mode 100644 index 0000000..29db878 --- /dev/null +++ b/jank-build-util/test/jank/build/util_test.clj @@ -0,0 +1,36 @@ +(ns jank.build.util-test + (:require + [clojure.test :refer [deftest is]] + [jank.build.util :as util])) + +(deftest os-detection-test + (with-redefs [util/os-name "Linux"] + (is (util/linux?)) + (is (not (util/mac?))) + (is (not (util/windows?)))) + (with-redefs [util/os-name "Mac OS X"] + (is (util/mac?)) + (is (not (util/linux?))) + (is (not (util/windows?)))) + (with-redefs [util/os-name "Windows XP"] + (is (util/windows?)) + (is (not (util/linux?))) + (is (not (util/mac?))))) + +(deftest build-directives-test + (is (= (with-out-str (util/define "KEY")) + "jank-build::define=KEY\n")) + (is (= (with-out-str (util/define "KEY" "VAL")) + "jank-build::define=KEY=VAL\n")) + (is (= (with-out-str (util/include-dir "/usr/include")) + "jank-build::include-dir=/usr/include\n")) + (is (= (with-out-str (util/link-dir "/usr/lib64")) + "jank-build::link-dir=/usr/lib64\n")) + (is (= (with-out-str (util/link-library "m")) + "jank-build::link-library=m\n")) + (is (= (with-out-str (util/link-framework "Cocoa")) + "jank-build::link-framework=Cocoa\n")) + (is (= (with-out-str (util/rerun-if-changed "/home")) + "jank-build::rerun-if-changed=/home\n")) + (is (= (with-out-str (util/rerun-if-env-changed "PATH")) + "jank-build::rerun-if-env-changed=PATH\n"))) From a704e01b7a91c1055b78d15a513afcd26672e1ea Mon Sep 17 00:00:00 2001 From: Kyle Cesare Date: Thu, 8 Oct 2026 17:49:32 -0600 Subject: [PATCH 2/4] Merge all jank-build-* into jank-build, add tests, cleanup --- jank-build-util/project.clj | 7 -- {jank-build-util => jank-build}/README.md | 6 +- jank-build/project.clj | 9 +++ jank-build/src/jank/build/cmake.clj | 33 ++++++++++ jank-build/src/jank/build/pkg_config.clj | 66 +++++++++++++++++++ .../src/jank/build/util.clj | 0 .../test-data/cmake-project/CMakeLists.txt | 3 + jank-build/test-data/cmake-project/foo.cpp | 4 ++ jank-build/test/jank/build/cmake_test.clj | 15 +++++ .../test/jank/build/pkg_config_test.clj | 11 ++++ .../test/jank/build/util_test.clj | 5 +- 11 files changed, 146 insertions(+), 13 deletions(-) delete mode 100644 jank-build-util/project.clj rename {jank-build-util => jank-build}/README.md (63%) create mode 100644 jank-build/project.clj create mode 100644 jank-build/src/jank/build/cmake.clj create mode 100644 jank-build/src/jank/build/pkg_config.clj rename {jank-build-util => jank-build}/src/jank/build/util.clj (100%) create mode 100644 jank-build/test-data/cmake-project/CMakeLists.txt create mode 100644 jank-build/test-data/cmake-project/foo.cpp create mode 100644 jank-build/test/jank/build/cmake_test.clj create mode 100644 jank-build/test/jank/build/pkg_config_test.clj rename {jank-build-util => jank-build}/test/jank/build/util_test.clj (93%) diff --git a/jank-build-util/project.clj b/jank-build-util/project.clj deleted file mode 100644 index c285768..0000000 --- a/jank-build-util/project.clj +++ /dev/null @@ -1,7 +0,0 @@ -(defproject org.jank-lang.commons/jank-build-util "0.1-SNAPSHOT" - :description "Build script helper functions" - :url "https://github.com/jank-lang/commons" - :license {:name "MPL 2.0" - :url "https://www.mozilla.org/en-US/MPL/2.0/"} - :source-paths ["src" "test"] - :profiles {:dev {:dependencies [[org.clojure/clojure "1.12.0"]]}}) diff --git a/jank-build-util/README.md b/jank-build/README.md similarity index 63% rename from jank-build-util/README.md rename to jank-build/README.md index 273b1ca..4245546 100644 --- a/jank-build-util/README.md +++ b/jank-build/README.md @@ -1,10 +1,10 @@ -# jank-build-util -[![Clojars Project](https://img.shields.io/clojars/v/org.jank-lang.commons/jank-build-util.svg)] +# jank-build +[![Clojars Project](https://img.shields.io/clojars/v/org.jank-lang.commons/jank-build.svg)] Build script helper functions. ## Usage Add this project to your Leiningen `:build-dependencies` vector: ```clojure -[org.jank-lang.commons/jank-build-util "0.1-SNAPSHOT"] +[org.jank-lang.commons/jank-build "0.1-SNAPSHOT"] ``` diff --git a/jank-build/project.clj b/jank-build/project.clj new file mode 100644 index 0000000..72422a4 --- /dev/null +++ b/jank-build/project.clj @@ -0,0 +1,9 @@ +(defproject org.jank-lang.commons/jank-build "0.1-SNAPSHOT" + :description "Build script helper functions" + :url "https://github.com/jank-lang/commons" + :license {:name "MPL 2.0" + :url "https://www.mozilla.org/en-US/MPL/2.0/"} + :source-paths ["src" "test"] + :profiles {:dev {:dependencies [[org.clojure/clojure "1.12.6"] + [babashka/fs "0.5.34"] + [babashka/process "0.6.25"]]}}) diff --git a/jank-build/src/jank/build/cmake.clj b/jank-build/src/jank/build/cmake.clj new file mode 100644 index 0000000..8a86d6f --- /dev/null +++ b/jank-build/src/jank/build/cmake.clj @@ -0,0 +1,33 @@ +(ns jank.build.cmake + (:require [babashka.process :as proc] + [jank.build.util :as util])) + +(defn install-rpath [] + (when (util/linux?) "$ORIGIN")) + +(defn default-defines [{:keys [out-dir optimization-level static?] + :or {optimization-level 0}}] + (let [rpath (install-rpath)] + (merge {"BUILD_SHARED_LIBS" (if static? "OFF" "ON") + "CMAKE_BUILD_TYPE" (if (pos? optimization-level) "Release" "Debug") + "CMAKE_INSTALL_PREFIX" out-dir + ;; Make sure these paths are standardized, otherwise libs may + ;; sometimes go into lib64/ instea of lib/, etc. + "CMAKE_INSTALL_BINDIR" "bin" + "CMAKE_INSTALL_LIBDIR" "lib" + "CMAKE_INSTALL_INCLUDEDIR" "include"} + (when (and (not static?) rpath) + {"CMAKE_INSTALL_RPATH" rpath})))) + +(defn cmake + "Run the `cmake` tool in the src-dir, building to the intermediate build-dir, + and installing artifacts into the out-dir. + + No jank-build directives are output, so make sure to specify the correct + link/include directories, link libraries, etc. yourself." + [{:keys [src-dir build-dir] :as input} + {:keys [defines target] :or {target "install"}}] + (let [d-flags (map (fn [[k v]] (str "-D" (name k) "=" v)) + (merge (default-defines input) defines))] + (proc/shell (concat ["cmake"] d-flags ["-B" build-dir src-dir])) + (proc/shell ["cmake" "--build" build-dir "--parallel" "--target" target]))) diff --git a/jank-build/src/jank/build/pkg_config.clj b/jank-build/src/jank/build/pkg_config.clj new file mode 100644 index 0000000..3e33311 --- /dev/null +++ b/jank-build/src/jank/build/pkg_config.clj @@ -0,0 +1,66 @@ +(ns jank.build.pkg-config + (:require [clojure.string :as string] + [babashka.process :as proc] + [babashka.fs :as fs] + [jank.build.util :as util])) + +(defn parse-prefixed [prefix s] + (for [entry (string/split s #"\s+") + :when (string/starts-with? entry prefix)] + (subs entry (count prefix)))) + +(defn has-static-lib? [lib link-dirs] + ; TODO: Windows? + (let [lib-name (str "lib" lib ".a")] + (some (fn [link-dir] + (let [link-dir+lib (fs/path link-dir lib-name)] + (when (fs/exists? link-dir+lib) + true))) + link-dirs))) + +(defn brew-pkg-config-path [build-input pkg] + (when (and (util/mac?) (fs/which "brew")) + (let [{:keys [exit out]} (proc/shell {:out :string + :err :string + :continue true + ; Disable brew's cache and use the already-authorized + ; working directory rather than /private/tmp. + :extra-env {"HOMEBREW_NO_BOOTSNAP" "1" + "HOMEBREW_NO_AUTO_UPDATE" "1" + "HOMEBREW_TEMP" (:out-dir build-input)}} + "brew" "--prefix") + prefix (string/trim out)] + (when (and (zero? exit) (not (string/blank? prefix))) + (str prefix "/opt/" pkg "/lib/pkgconfig"))))) + +(defn pkg-config-path + "Rebuild the PKG_CONFIG_PATH string with additional paths prepended. nil + entries are ignored." + [& paths] + (let [sep (System/getProperty "path.separator") + from-env (some-> (System/getenv "PKG_CONFIG_PATH") + (not-empty) + (string/split (re-pattern sep))) + fresh (filter some? paths)] + (string/join sep (concat fresh from-env)))) + +(defn pkg-config + "Call the `pkg-config` tool and parse link directories, include directories, + and link libraries. + + jank-build directives will automatically be output for these values." + [build-input pc-name] + ;; TODO: parse preprocessor defines from cflags + (let [pc-cmd (cond-> ["pkg-config" pc-name "--libs" "--cflags"] + (:static? build-input) (conj "--static")) + brew-path (brew-pkg-config-path build-input pc-name) + pc-opts {:extra-env {"PKG_CONFIG_PATH" (pkg-config-path brew-path)} + :out :string} + pc-output (->> pc-cmd (apply proc/shell pc-opts) :out) + link-dirs (parse-prefixed "-L" pc-output)] + (run! util/link-dir link-dirs) + (run! util/include-dir (parse-prefixed "-I" pc-output)) + (run! #(if (and (:static? build-input) (has-static-lib? % link-dirs)) + (util/link-static-library %) + (util/link-library %)) + (parse-prefixed "-l" pc-output)))) diff --git a/jank-build-util/src/jank/build/util.clj b/jank-build/src/jank/build/util.clj similarity index 100% rename from jank-build-util/src/jank/build/util.clj rename to jank-build/src/jank/build/util.clj diff --git a/jank-build/test-data/cmake-project/CMakeLists.txt b/jank-build/test-data/cmake-project/CMakeLists.txt new file mode 100644 index 0000000..9dae37b --- /dev/null +++ b/jank-build/test-data/cmake-project/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.12) +project(cmake_project VERSION 1.0 LANGUAGES CXX) +add_library(foo foo.cpp) diff --git a/jank-build/test-data/cmake-project/foo.cpp b/jank-build/test-data/cmake-project/foo.cpp new file mode 100644 index 0000000..b85b60d --- /dev/null +++ b/jank-build/test-data/cmake-project/foo.cpp @@ -0,0 +1,4 @@ +int foo() +{ + return 42; +} diff --git a/jank-build/test/jank/build/cmake_test.clj b/jank-build/test/jank/build/cmake_test.clj new file mode 100644 index 0000000..dec4153 --- /dev/null +++ b/jank-build/test/jank/build/cmake_test.clj @@ -0,0 +1,15 @@ +(ns jank.build.cmake-test + (:require [clojure.test :refer [deftest is]] + [clojure.string :as string] + [babashka.fs :as fs] + [jank.build.cmake :as cm])) + +(def test-project-dir (fs/path "test-data/cmake-project")) + +(deftest build-example-project-test + ;; should not throw + (cm/cmake {:src-dir test-project-dir + :build-dir (fs/create-temp-dir {:prefix "jank-build-cmake-test"}) + :out-dir (fs/create-temp-dir {:prefix "jank-build-cmake-test"})} + {:defines {:SOME_DEFINE true} + :target "all"})) diff --git a/jank-build/test/jank/build/pkg_config_test.clj b/jank-build/test/jank/build/pkg_config_test.clj new file mode 100644 index 0000000..d63c509 --- /dev/null +++ b/jank-build/test/jank/build/pkg_config_test.clj @@ -0,0 +1,11 @@ +(ns jank.build.pkg-config-test + (:require [clojure.test :refer [deftest is]] + [clojure.string :as string] + [jank.build.pkg-config :as pc])) + +;; zlib should be on all jank-enabled systems. +(deftest find-zlib-test + (let [out (with-out-str *out* (pc/pkg-config {} "zlib"))] + (is (string/includes? out "jank-build::link-dir=")) + (is (string/includes? out "jank-build::include-dir=")) + (is (string/includes? out "jank-build::link-library=z")))) diff --git a/jank-build-util/test/jank/build/util_test.clj b/jank-build/test/jank/build/util_test.clj similarity index 93% rename from jank-build-util/test/jank/build/util_test.clj rename to jank-build/test/jank/build/util_test.clj index 29db878..c691e8b 100644 --- a/jank-build-util/test/jank/build/util_test.clj +++ b/jank-build/test/jank/build/util_test.clj @@ -1,7 +1,6 @@ (ns jank.build.util-test - (:require - [clojure.test :refer [deftest is]] - [jank.build.util :as util])) + (:require [clojure.test :refer [deftest is]] + [jank.build.util :as util])) (deftest os-detection-test (with-redefs [util/os-name "Linux"] From 58ed31411c11832223e6638242fd0941b79a5f75 Mon Sep 17 00:00:00 2001 From: Kyle Cesare Date: Sat, 10 Oct 2026 14:28:15 -0600 Subject: [PATCH 3/4] Allow empty cmake options map. --- jank-build/src/jank/build/cmake.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jank-build/src/jank/build/cmake.clj b/jank-build/src/jank/build/cmake.clj index 8a86d6f..37245e2 100644 --- a/jank-build/src/jank/build/cmake.clj +++ b/jank-build/src/jank/build/cmake.clj @@ -25,8 +25,7 @@ No jank-build directives are output, so make sure to specify the correct link/include directories, link libraries, etc. yourself." - [{:keys [src-dir build-dir] :as input} - {:keys [defines target] :or {target "install"}}] + [{:keys [src-dir build-dir] :as input} & {:keys [defines target] :or {target "install"}}] (let [d-flags (map (fn [[k v]] (str "-D" (name k) "=" v)) (merge (default-defines input) defines))] (proc/shell (concat ["cmake"] d-flags ["-B" build-dir src-dir])) From d1d3c01851a0775c214a1f2a9f4c4b2b02e87739 Mon Sep 17 00:00:00 2001 From: Kyle Cesare Date: Sat, 10 Oct 2026 14:28:26 -0600 Subject: [PATCH 4/4] Document include-dir/link-dir relative path behavior. --- jank-build/src/jank/build/util.clj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jank-build/src/jank/build/util.clj b/jank-build/src/jank/build/util.clj index 0b5590c..24ff88f 100644 --- a/jank-build/src/jank/build/util.clj +++ b/jank-build/src/jank/build/util.clj @@ -26,10 +26,16 @@ ([k] (println (str "jank-build::define=" k))) ([k v] (println (str "jank-build::define=" k "=" v)))) -(defn include-dir [dir] +(defn include-dir + "Add a jank include-dir directive (a -I flag). If a relative path is given + then it is assumed to be relative to the build output directory." + [dir] (println (str "jank-build::include-dir=" dir))) -(defn link-dir [dir] +(defn link-dir + "Add a jank link-dir directive (a -L flag). If a relative path is given then + it is assumed to be relative to the build output directory." + [dir] (println (str "jank-build::link-dir=" dir))) (defn link-library [lib]