├── .clj-kondo
├── config.edn
└── imports
│ ├── babashka
│ └── fs
│ │ └── config.edn
│ ├── http-kit
│ └── http-kit
│ │ ├── config.edn
│ │ └── httpkit
│ │ └── with_channel.clj
│ ├── lread
│ └── status-line
│ │ └── config.edn
│ ├── rewrite-clj
│ └── rewrite-clj
│ │ └── config.edn
│ └── taoensso
│ └── encore
│ ├── config.edn
│ └── taoensso
│ └── encore.clj
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bb.edn
├── build-helper
├── deps.edn
└── src
│ └── build_helper
│ └── core.clj
├── build.clj
├── build_shared.clj
├── deps.edn
├── resources
└── clj-easy
│ └── graal-build-time-version.txt
├── scripts
├── bump_version.clj
└── lint.clj
├── src
└── clj_easy
│ └── graal_build_time
│ ├── InitClojureClasses.java
│ └── packages.clj
├── test-hello-world
├── bb.edn
├── build.clj
├── deps.edn
├── lib1
│ ├── bb.edn
│ ├── build.clj
│ ├── deps.edn
│ └── src
│ │ └── gbt_test_org
│ │ └── p2
│ │ └── core.clj
├── lib2
│ ├── bb.edn
│ ├── build.clj
│ ├── deps.edn
│ └── src
│ │ └── gbt_test_org
│ │ └── core.clj
└── src
│ ├── hello
│ └── core.clj
│ └── hello_world
│ └── main.clj
├── test-single-segment
├── bb.edn
├── build.clj
├── deps.edn
└── src
│ └── single_segment
│ └── main.clj
└── test
└── clj_easy
└── graal_build_time
└── packages_test.clj
/.clj-kondo/config.edn:
--------------------------------------------------------------------------------
1 | {:config-paths ^:replace [] ;; don't include user configs
2 | }
3 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/babashka/fs/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}}
2 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/http-kit/http-kit/config.edn:
--------------------------------------------------------------------------------
1 |
2 | {:hooks
3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}}
4 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/http-kit/http-kit/httpkit/with_channel.clj:
--------------------------------------------------------------------------------
1 | (ns httpkit.with-channel
2 | (:require [clj-kondo.hooks-api :as api]))
3 |
4 | (defn with-channel [{node :node}]
5 | (let [[request channel & body] (rest (:children node))]
6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {})))
7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {})))
8 | (let [new-node
9 | (api/list-node
10 | (list*
11 | (api/token-node 'let)
12 | (api/vector-node [channel (api/vector-node [])])
13 | request
14 | body))]
15 |
16 | {:node new-node})))
17 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/lread/status-line/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {lread.status-line/line clojure.tools.logging/infof
2 | lread.status-line/die clojure.tools.logging/infof}}
3 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/rewrite-clj/rewrite-clj/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as
2 | {rewrite-clj.zip/subedit-> clojure.core/->
3 | rewrite-clj.zip/subedit->> clojure.core/->>
4 | rewrite-clj.zip/edit-> clojure.core/->
5 | rewrite-clj.zip/edit->> clojure.core/->>}}
6 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/taoensso/encore/config.edn:
--------------------------------------------------------------------------------
1 | {:hooks
2 | {:analyze-call
3 | {taoensso.encore/defalias taoensso.encore/defalias
4 | taoensso.encore/defn-cached taoensso.encore/defn-cached
5 | taoensso.encore/defonce taoensso.encore/defonce}}}
6 |
--------------------------------------------------------------------------------
/.clj-kondo/imports/taoensso/encore/taoensso/encore.clj:
--------------------------------------------------------------------------------
1 | (ns taoensso.encore
2 | "I don't personally use clj-kondo, so these hooks are
3 | kindly authored and maintained by contributors.
4 | PRs very welcome! - Peter Taoussanis"
5 | (:refer-clojure :exclude [defonce])
6 | (:require
7 | [clj-kondo.hooks-api :as hooks]))
8 |
9 | (defn defalias
10 | [{:keys [node]}]
11 | (let [[sym-raw src-raw] (rest (:children node))
12 | src (or src-raw sym-raw)
13 | sym (if src-raw sym-raw (symbol (name (hooks/sexpr src))))]
14 | {:node
15 | (with-meta
16 | (hooks/list-node
17 | [(hooks/token-node 'def)
18 | (hooks/token-node (hooks/sexpr sym))
19 | (hooks/token-node (hooks/sexpr src))])
20 | (meta src))}))
21 |
22 | (defn defn-cached
23 | [{:keys [node]}]
24 | (let [[sym _opts binding-vec & body] (rest (:children node))]
25 | {:node
26 | (hooks/list-node
27 | (list
28 | (hooks/token-node 'def)
29 | sym
30 | (hooks/list-node
31 | (list*
32 | (hooks/token-node 'fn)
33 | binding-vec
34 | body))))}))
35 |
36 | (defn defonce
37 | [{:keys [node]}]
38 | ;; args = [sym doc-string? attr-map? init-expr]
39 | (let [[sym & args] (rest (:children node))
40 | [doc-string args] (if (and (hooks/string-node? (first args)) (next args)) [(hooks/sexpr (first args)) (next args)] [nil args])
41 | [attr-map init-expr] (if (and (hooks/map-node? (first args)) (next args)) [(hooks/sexpr (first args)) (fnext args)] [nil (first args)])
42 |
43 | attr-map (if doc-string (assoc attr-map :doc doc-string) attr-map)
44 | sym+meta (if attr-map (with-meta sym attr-map) sym)
45 | rewritten
46 | (hooks/list-node
47 | [(hooks/token-node 'clojure.core/defonce)
48 | sym+meta
49 | init-expr])]
50 |
51 | {:node rewritten}))
52 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Test and Deploy
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout code
10 | uses: actions/checkout@v4
11 |
12 | - name: Setup Java
13 | uses: actions/setup-java@v4
14 | with:
15 | distribution: 'temurin'
16 | java-version: '21'
17 |
18 | - name: Setup Babashka
19 | uses: DeLaGuardo/setup-clojure@13.2
20 | with:
21 | bb: 'latest'
22 |
23 | - name: Apply Cache
24 | uses: actions/cache@v4
25 | with:
26 | path: |
27 | ~/.m2/repository
28 | ~/.deps.clj
29 | ~/.gitlibs
30 | key: lint-graal-build-time-${{ hashFiles('deps.edn','bb.edn') }}
31 | restore-keys: "lint-graal-build-time-"
32 |
33 | - name: Lint
34 | run: bb lint
35 |
36 | test:
37 | runs-on: ${{ matrix.os }}-latest
38 | strategy:
39 | fail-fast: false
40 | matrix:
41 | os:
42 | - 'ubuntu'
43 | - 'windows'
44 | distribution:
45 | - 'graalvm'
46 | - 'graalvm-community'
47 | java-version:
48 | - '24'
49 | steps:
50 | - name: Checkout code
51 | uses: actions/checkout@v4
52 |
53 | - name: Setup GraalVM
54 | uses: graalvm/setup-graalvm@v1
55 | with:
56 | java-version: ${{ matrix.java-version }}
57 | distribution: ${{ matrix.distribution }}
58 | github-token: ${{ secrets.GITHUB_TOKEN }}
59 |
60 | - name: Setup Babashka
61 | uses: DeLaGuardo/setup-clojure@13.2
62 | with:
63 | bb: 'latest'
64 |
65 | - name: Tools versions
66 | run: |
67 | java --version
68 | bb --version
69 |
70 | - name: Apply Cache
71 | uses: actions/cache@v4
72 | with:
73 | path: |
74 | ~/.m2/repository
75 | ~/.deps.clj
76 | ~/.gitlibs
77 | key: ${{ runner.os }}-graal-build-time-${{ hashFiles('**/deps.edn') }}
78 | restore-keys: ${{ runner.os }}-graal-build-time-
79 |
80 | - name: Run tests
81 | run: bb test
82 |
83 | - name: Run native tests
84 | run: bb native-image-test
85 |
86 | deploy:
87 | needs: [lint, test]
88 | if: github.ref == 'refs/heads/main'
89 | runs-on: ubuntu-latest
90 |
91 | steps:
92 | - name: Checkout code
93 | uses: actions/checkout@v4
94 |
95 | - name: Setup Babashka
96 | uses: DeLaGuardo/setup-clojure@13.2
97 | with:
98 | bb: 'latest'
99 |
100 | # any JDK > JDK8 will do to compile sources, but let's deliberately choose one
101 | - name: Setup Java
102 | uses: actions/setup-java@v4
103 | with:
104 | distribution: 'temurin'
105 | java-version: '21'
106 |
107 | - name: Tools versions
108 | run: |
109 | java --version
110 | bb --version
111 |
112 | - name: Apply Cache
113 | uses: actions/cache@v4
114 | with:
115 | path: |
116 | ~/.m2/repository
117 | ~/.deps.clj
118 | ~/.gitlibs
119 | key: ${{ runner.os }}-graal-build-time-${{ hashFiles('**/deps.edn') }}
120 | restore-keys: ${{ runner.os }}-graal-build-time-
121 |
122 | - name: Deploy to clojars
123 | env:
124 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_ORG_USERNAME }}
125 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_ORG_PASSWORD }}
126 | run: bb deploy
127 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | .cpcache
3 | .clj-kondo/.cache
4 | .lsp
5 | !.lsp/config.edn
6 | .nrepl-port
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 |
4 | ## Unreleased
5 |
6 | - Add license (and other common entries) to library pom file [#44](https://github.com/clj-easy/graal-build-time/issues/44)
7 | - Minor
8 | - Build, test and release `graal-build-time` with current version of Clojure [#46](https://github.com/clj-easy/graal-build-time/issues/46)
9 |
10 | ## v1.0.5
11 |
12 | - ⚠️ _**You'll need to update your `native-image` command line**_ ⚠️ - Adapt to GraalVM deprecation
13 | - The Graal team has deprecated automatic discovery of GraalVM `native-image` `Feature` classes. They feel it is safer for you to explicitly opt in when enabling features.
14 | - Since `graal-build-time` was implemented as an auto-discovered Feature class, we have adapted to this change.
15 | - After upgrading, you need to add `--features=clj_easy.graal_build_time.InitClojureClasses` to your `native-image` command line.
16 |
17 | ## v0.1.4
18 |
19 | - Our output lines during a `native-image` build are now identified with prefix `[clj-easy/graal-build-time]` [#18](https://github.com/clj-easy/graal-build-time/pull/18)
20 | - Child package `hello.world` from classpath element x is now properly excluded when parent package `hello` occurs in classpath element y [#17](https://github.com/clj-easy/graal-build-time/pull/17)
21 | - Add source control management (scm) info within our jar so that clojars and others can point back to our github repo [#19](https://github.com/clj-easy/graal-build-time/pull/19)
22 |
23 | ## v0.1.3
24 |
25 | - Package `hello_world` is no longer excluded due to erroneously being considered a child of package `hello` [#12](https://github.com/clj-easy/graal-build-time/pull/12)
26 |
27 | ## v0.1.2
28 |
29 | - Stop asking `native-image` to register blank packages, it ascribes this to mean a request to register all packages [#11](https://github.com/clj-easy/graal-build-time/pull/11)
30 | - Properly analyze `clojure.*` packages on Windows when working from jar file [#11](https://github.com/clj-easy/graal-build-time/pull/11)
31 |
32 | ## v0.1.0
33 |
34 | - First 0.1.x release!
35 |
36 | ## v0.0.x
37 |
38 | - Initial test releases
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-2024 CLJ Easy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://clojars.org/com.github.clj-easy/graal-build-time)
2 | [](https://clojurians.slack.com/archives/C02DQFVS0MC)
3 |
4 | # graal-build-time
5 |
6 | Automatically detect and initialize Clojure classes during `native-image` compilation.
7 |
8 | > ⬆️ **Upgrading to from v0 to v1?**
9 | > Add `--features=clj_easy.graal_build_time.InitClojureClasses` to your `native-image` command line.
10 |
11 | We compile our Clojure sources to `.class` files so that GraalVM `native-image` can in turn compile them into a binary executable.
12 | Due to their nature, Clojure `.class` files typically must be initialized at build time by GraalVM `native-image`.
13 | If they are not initialized at build time, GraalVM will still create your binary executable, but when you run it, it probably won't work.
14 | You will likely see it fail with an error that includes:
15 | ```
16 | java.io.FileNotFoundException: Could not locate clojure/core__init.class, clojure/core.clj or clojure/core.cljc on classpath
17 | ```
18 |
19 | In the early days, our solution was to use `native-image`'s `--initialize-at-build-time` to globally initialize all classes at build time.
20 | Because global initialization of all classes can be problematic, GraalVM deprecated this usage.
21 | Instead, you must be explicit and specify the packages of the classes you need to initialize at build time.
22 | For example: `--initialize-at-build-time=clojure,my_library,etc...`
23 | But, this can be tedious and error-prone.
24 |
25 | Hence, `graal-build-time`.
26 | This library automatically detects `.class` files created by Clojure and asks `native-image` to initialize them at build time.
27 |
28 | ## Usage
29 |
30 | We assume you are using the current stable release of:
31 | - [GraalVM Community Edition](https://github.com/graalvm/graalvm-ce-builds/releases/) or [GraalVM Oracle](https://www.graalvm.org/downloads/)
32 | - [Clojure](https://clojure.org/guides/install_clojure)
33 |
34 | We don't test against or support older releases.
35 |
36 | For your `native-image` build:
37 | 1. If you are using `--initialize-at-build-time`, remove it.
38 | 2. Add `--features=clj_easy.graal_build_time.InitClojureClasses`
39 | 3. Include the `graal-build-time` library on your classpath.
40 | This is typically done by adding this library to your project dependencies (see the clojars link above).
41 |
42 | During the `native-image` build process, you will see a line of output from `graal-build-time` describing the packages it has detected:
43 |
44 | ```
45 | [clj-easy/graal-build-time] Registering packages for build time initialization: clojure, clj_easy.graal_build_time
46 | ```
47 |
48 | ## How it works
49 |
50 | `graal-build-time` hooks into the GraalVM `native-image` build process via a GraalVM `Feature` class.
51 | It inspects the classpath.
52 | Each class file that ends with `__init.class` is assumed to have been created by Clojure.
53 | The packages of these classes are then added to the list of packages to be initialized at build time.
54 |
55 | ## Overriding classes
56 |
57 | If there are classes in packages that you would like to initialize at runtime, you can
58 | use the `--initialize-at-run-time=my.org.MyClass` `native-image` argument.
59 |
60 | For example, when using http-kit, the `org.httpkit` package will be included for
61 | build-time initialization. You will need to override one class via `native-image` argument:
62 | ```
63 | --initialize-at-run-time=org.httpkit.client.ClientSslEngineFactory$SSLHolder
64 | ```
65 |
66 | ## Single segment namespaces
67 |
68 | This library doesn't work with single segment namespaces.
69 | A single segment namespace is one without any `.` characters in it, for example: `(ns digest)`.
70 |
71 | A single segment namespace, becomes, in the eyes of the JVM, package-less and ends up in the JVM default package.
72 | Single segment namespaces are problematic in general in Clojure and, because they are package-less, will not be initialized by `graal-build-time`.
73 |
74 | `graal-build-time` will emit a warning when it detects `.class` files generated from a single segment namespace, for example:
75 |
76 | ```
77 | [clj-easy/graal-build-time] WARN: Single segment namespace found for class: digest__init.class. Because this class has no package, it cannot be registered for initialization at build time.
78 | ```
79 |
80 | Starting with GraalVM v22, because it enables`--strict-image-heap` by default, you'll also see `native-image` fail your build when single segment namespaces are present.
81 | See [#35](https://github.com/clj-easy/graal-build-time/issues/35) for details if you are curious.
82 |
83 | ## Develop
84 |
85 | Run `bb tasks` for all relevant project tasks.
86 |
87 | Tasks attempt to avoid unnecessary work by comparing source and target file dates.
88 | If you want to skip this optimization, run `bb clean` before running your task.
89 |
90 | Use `bb native-image-test` to run our integration tests.
91 | - This task builds native images for a hello world app, and then runs them.
92 | - The hello world Clojure sources are compiled to Java classes.
93 | - We use GraalVM's `native-image` with `graal-build-time` on the classpath to create 2 variants of the same app:
94 | - one built from the uberjar
95 | - the other built directly from the classes dir
96 | - Note that we omit `--initialize-at-build-time` when creating the native images.
97 | The work that this deprecated option carried out is now taken care of by `graal-build-time`.
98 | - During native image creation, you'll see output that looks like:
99 |
100 | ```
101 | [clj-easy/graal-build-time] Registering packages for build time initialization: clojure, clj_easy.graal_build_time, gbt_test_org, hello, hello_world
102 | ```
103 |
104 | # License
105 |
106 | Licensed under the MIT license, see LICENSE.
107 |
108 | Copyright © 2021-2024 Michiel Borkent, Eric Dallo, Rahul Dé, Lee Read and contributors.
109 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:min-bb-version "1.3.182"
2 | :paths ["." "scripts"]
3 | :deps {lread/status-line {:git/url "https://github.com/lread/status-line.git"
4 | :sha "cf44c15f30ea3867227fa61ceb823e5e942c707f"}}
5 | :tasks
6 | {:requires ([babashka.fs :as fs]
7 | [babashka.tasks :as tasks]
8 | [babashka.process :refer [tokenize]]
9 | [build-shared :as bs]
10 | [clojure.string :as str])
11 | :enter (when (not (str/starts-with? (:name (current-task)) "-"))
12 | (println (format "-[graal-build-time %s]----" (:name (current-task)))))
13 | :init
14 | (do
15 | (def windows? (str/starts-with? (System/getProperty "os.name")
16 | "Windows"))
17 | (defn clojure
18 | "Clojure defaulting to stderr output"
19 | [arg & args]
20 | (apply tasks/clojure (str "-J-Dclojure.main.report=stderr " arg) args)))
21 |
22 | clean {:doc "Clean target dir"
23 | :task (do
24 | (doseq [dir ["target" ".cpcache"]]
25 | (fs/delete-tree dir))
26 | (doseq [dir ["test-hello-world" "test-single-segment"]]
27 | (shell {:dir dir} "bb clean")))}
28 |
29 | compile-sources {:doc "Compile sources"
30 | :task (if (seq (fs/modified-since bs/class-dir ["deps.edn" "src"]))
31 | (clojure "-T:build compile-sources")
32 | (println "Sources already compiled to" bs/class-dir))}
33 |
34 | jar {:doc "Build jar"
35 | :depends [compile-sources]
36 | :task (if (seq (fs/modified-since bs/jar-file [bs/class-dir]))
37 | (clojure "-T:build jar")
38 | (println "Jar is already up to date" bs/jar-file))}
39 |
40 | jar-name {:doc "Return jar name for test-hello-world build"
41 | :task (println bs/jar-file)}
42 |
43 | install {:doc "Install jar in local maven repo"
44 | :depends [jar]
45 | :task (clojure "-T:build install")}
46 |
47 | build-hello-world {:doc "Build jars, classes for testing"
48 | :task (shell {:dir "test-hello-world"}
49 | "bb build")}
50 |
51 | build-single-segment {:doc "Build jars, classes for single segment testing"
52 | :task (shell {:dir "test-single-segment"}
53 | "bb build")}
54 |
55 | test {:doc "Runs tests"
56 | :task (clojure "-X:test")}
57 |
58 | native-image-test {:doc "Runs native image hello world tests"
59 | :task (shell {:dir "test-hello-world"}
60 | "bb native-image-test")}
61 |
62 | lint {:doc "[--rebuild] Lint source code with clj-kondo"
63 | :task lint/-main}
64 |
65 | outdated {:doc "Report on outdated dependencies"
66 | :task (clojure "-M:outdated" (str "--directory=" (str/join ":" ["."
67 | "test-hello-world"
68 | "test-hello-world/lib1"
69 | "test-hello-world/lib2"
70 | "test-single-segment"
71 | "build-helper"])))}
72 |
73 | change-log-check {:doc "Checks that change log is ready for publish"
74 | :task (bs/change-log-check)}
75 |
76 | -change-log-update {:doc "Updates change log for current version"
77 | :depends [change-log-check]
78 | :task (bs/change-log-update)}
79 |
80 | -bump-version (do
81 | (bs/refresh-version)
82 | (println "Bumping current version" bs/version)
83 | (load-file "scripts/bump_version.clj")
84 | (bs/refresh-version)
85 | (println "Bumped version to" bs/version))
86 |
87 | -commit-release (do
88 | (println "Committing changes made for release")
89 | (shell "git add resources/clj-easy/graal-build-time-version.txt CHANGELOG.md")
90 | (shell "git commit -m 'Bump version'"))
91 |
92 | -tag (do
93 | (println (str "Tagging with version v" bs/version))
94 | (shell (str "git tag v" bs/version)))
95 |
96 | publish {:doc "Cut a new release"
97 | :task (do
98 | (run 'change-log-check)
99 | (run '-bump-version)
100 | (run '-change-log-update)
101 | (run '-commit-release)
102 | (run '-tag)
103 | (println "Pushing commit to github")
104 | (shell "git push --atomic origin main"
105 | (str "v" bs/version)))}
106 |
107 | -current-branch (->> (shell {:out :string} "git rev-parse --abbrev-ref HEAD")
108 | :out
109 | str/trim)
110 |
111 | -can-release {:depends [-current-branch]
112 | :task (= "main" -current-branch)}
113 |
114 | deploy {:doc "Deploys to clojars, automatically run on CI"
115 | :depends [-can-release]
116 | :task (if -can-release
117 | (do
118 | (run 'jar)
119 | (println "All set for deployment 🚀🚀")
120 | (tasks/clojure
121 | {:continue true}
122 | "-J-Dclojure.main.report=stderr -T:build deploy"))
123 | (println "Conditions for release did not pass, not deploying."))}}}
124 |
--------------------------------------------------------------------------------
/build-helper/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src"]
2 | :deps {io.github.clojure/tools.build {:mvn/version "0.10.7"}
3 | babashka/fs {:mvn/version "0.5.24"}}}
4 |
--------------------------------------------------------------------------------
/build-helper/src/build_helper/core.clj:
--------------------------------------------------------------------------------
1 | (ns build-helper.core
2 | "Reusable build support for internal test projects"
3 | (:require
4 | [clojure.string :as str]
5 | [clojure.tools.build.api :as b]))
6 |
7 | (defmacro with-err-str
8 | [& body]
9 | `(let [s# (new java.io.StringWriter)]
10 | (binding [*err* s#]
11 | ~@body
12 | (str s#))))
13 |
14 | (defn uber [{:keys [lib main uber-file]}]
15 | (let [class-dir "target/classes"
16 | src-dirs ["src"]
17 | basis (b/create-basis)]
18 | (println "Writing pom")
19 | (->> (b/write-pom {:class-dir class-dir
20 | :lib lib
21 | :version "1.0.0"
22 | :basis basis
23 | :src-dirs ["src"]})
24 | with-err-str
25 | str/split-lines
26 | ;; Avoid confusing future me/you: suppress "Skipping coordinate" messages for our jars, we don't care, we are creating an uberjar
27 | (remove #(re-matches #"^Skipping coordinate: \{:local/root .*target/(lib1|lib2|graal-build-time).jar.*" %))
28 | (run! println))
29 | (b/copy-dir {:src-dirs src-dirs
30 | :target-dir class-dir})
31 | (println "Compile sources to classes")
32 | (b/compile-clj {:basis basis
33 | :src-dirs src-dirs
34 | :class-dir class-dir
35 | :ns-compile [main]})
36 | (println "Building uberjar" uber-file)
37 | (b/uber {:class-dir class-dir
38 | :uber-file uber-file
39 | :basis basis
40 | :main main})))
41 |
42 | (defn jar [{:keys [lib jar-file]}]
43 | (let [class-dir "target/classes"
44 | src-dirs ["src"]
45 | basis (b/create-basis)]
46 | (println "Writing pom")
47 | (b/write-pom {:class-dir class-dir
48 | :lib lib
49 | :version "1.0.0"
50 | :basis basis
51 | :src-dirs ["src"]})
52 | (b/copy-dir {:src-dirs src-dirs
53 | :target-dir class-dir})
54 | (println "Compile sources to classes")
55 | (b/compile-clj {:basis basis
56 | :src-dirs src-dirs
57 | :class-dir class-dir})
58 | (println "Building jar" jar-file)
59 | (b/jar {:class-dir class-dir
60 | :jar-file jar-file
61 | :basis basis})))
62 |
--------------------------------------------------------------------------------
/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:refer-clojure :exclude [compile])
3 | (:require
4 | [build-shared :as bs :refer [lib]]
5 | [clojure.tools.build.api :as b]
6 | [deps-deploy.deps-deploy :as dd]))
7 |
8 | (def class-dir bs/class-dir)
9 | (def basis (b/create-basis {:project "deps.edn"}))
10 | (def with-svm-basis (b/create-basis {:project "deps.edn"
11 | :aliases [:svm]}))
12 | (def version bs/version)
13 | (def jar-file bs/jar-file)
14 |
15 | (defn compile-sources [{:keys [class-dir] :or {class-dir class-dir}}]
16 | (println "Compiling Clojure sources to:" class-dir)
17 | (b/compile-clj {:basis basis
18 | :src-dirs bs/sources
19 | :class-dir class-dir})
20 | (println "Done compiling Clojure sources.")
21 | (println "Compiling java sources.")
22 | (b/javac {:src-dirs bs/sources
23 | :class-dir class-dir
24 | :basis with-svm-basis
25 | :javac-opts ["--release" "8" ;; technically current Graal jdk version is our current jdk min,
26 | ;; but clojure produces jdk 8 compatible classes, so we'll arbitrarily
27 | ;; match that
28 | ;; --release was introduce after jdk8, so we'll fail
29 | ;; if compiling with <= jdk8, which is fine.
30 | "-Xlint:-options" "-Werror"]})
31 | (println "Done compiling java sources."))
32 |
33 | (defn jar [_]
34 | (println "Producing jar:" jar-file)
35 | (let [gh-coords "github.com/clj-easy/graal-build-time"
36 | project-url (str "https://" gh-coords)]
37 | (b/write-pom {:class-dir class-dir
38 | :lib lib
39 | :version version
40 | :scm {:connection (format "scm:git:git://%s.git" gh-coords)
41 | :developerConnection (format "scm:git:ssh://git@%s.git" gh-coords)
42 | :tag (format "v%s" version)
43 | :url project-url}
44 | :basis basis
45 | :src-dirs ["src"]
46 | :pom-data [[:description "Initialize Clojure classes at build time with GraalVM native-image"]
47 | [:url project-url]
48 | [:licenses
49 | [:license
50 | [:name "The MIT License"]
51 | [:url "https://opensource.org/licenses/MIT"]]]
52 | [:properties
53 | [:project.build.sourceEncoding "UTF-8"]]]}))
54 | (b/copy-dir {:src-dirs ["src" "resources"]
55 | :target-dir class-dir})
56 | (b/jar {:class-dir class-dir
57 | :jar-file jar-file})
58 | (println "Done building jar."))
59 |
60 | (defn install
61 | [_]
62 | (b/install {:basis basis
63 | :lib lib
64 | :version version
65 | :jar-file jar-file
66 | :class-dir class-dir})
67 | (println "Installed" lib version "in local maven repo."))
68 |
69 | (defn deploy [opts]
70 | (println "Deploying version" jar-file "to Clojars.")
71 | (dd/deploy (merge {:installer :remote
72 | :artifact jar-file
73 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})}
74 | opts))
75 | opts)
76 |
--------------------------------------------------------------------------------
/build_shared.clj:
--------------------------------------------------------------------------------
1 | (ns build-shared
2 | (:require [clojure.string :as str]))
3 |
4 | (def version-file "resources/clj-easy/graal-build-time-version.txt")
5 | (def version nil)
6 | (defn refresh-version []
7 | (alter-var-root #'version (constantly (str/trim (slurp version-file)))))
8 | (refresh-version)
9 |
10 | (def target "target")
11 | (def lib 'com.github.clj-easy/graal-build-time)
12 | (def jar-file (format "%s/%s-%s.jar" target (name lib) version))
13 | (def sources ["src"])
14 | (def class-dir (str target "/classes"))
15 |
16 | (defn change-log-check []
17 | (let [log (slurp "CHANGELOG.md")
18 | new-changes (last (re-find #"(?ms)^## Unreleased *$(.*?)^##|\z" log))]
19 | (if (not (and new-changes (re-find #"(?ims)[a-z]" new-changes)))
20 | (do
21 | (println "FAIL: Change log must contain Unreleased section with some text.")
22 | (System/exit 1))
23 | (println "PASS: Change log Unreleased section found with some text."))))
24 |
25 | (defn change-log-update []
26 | (println "Updating change log")
27 | (refresh-version)
28 | (let [log (slurp "CHANGELOG.md")
29 | new-log (str/replace-first
30 | log
31 | #"(?ms)^## Unreleased *$"
32 | (str "## Unreleased\n\n-\n\n## v" version))]
33 | (spit "CHANGELOG.md" new-log)
34 | (println (str "Change log updated for v" version))))
35 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {org.clojure/clojure {:mvn/version "1.12.0"}}
2 | :paths ["src" "resources"]
3 | :aliases
4 | {:svm
5 | ;; this library is "provided"
6 | {:extra-deps {org.graalvm.sdk/nativeimage {:mvn/version "24.2.0"}}}
7 | :build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.7"}
8 | babashka/fs {:mvn/version "0.5.24"}
9 | babashka/process {:mvn/version "0.5.22"}
10 | slipset/deps-deploy {:mvn/version "0.2.2"}}
11 | :ns-default build}
12 | :uber {:extra-paths ["test"]}
13 | :clj-kondo {:extra-deps {clj-kondo/clj-kondo {:mvn/version "2025.02.20"}}
14 | :main-opts ["-m" "clj-kondo.main"]}
15 | :test {:extra-paths ["test"]
16 | :extra-deps {io.github.cognitect-labs/test-runner
17 | {:git/tag "v0.5.1" :git/sha "dfb30dd"}
18 | babashka/process {:mvn/version "0.5.22"}}
19 | :exec-fn cognitect.test-runner.api/test}
20 | :outdated {:deps {org.slf4j/slf4j-simple {:mvn/version "2.0.17"} ;; to rid ourselves of logger warnings
21 | com.github.liquidz/antq {:mvn/version "2.11.1276"}}
22 | :main-opts ["-m" "antq.core"]}}}
23 |
--------------------------------------------------------------------------------
/resources/clj-easy/graal-build-time-version.txt:
--------------------------------------------------------------------------------
1 | 1.0.5
--------------------------------------------------------------------------------
/scripts/bump_version.clj:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bb
2 |
3 | (ns bump-version
4 | (:require [clojure.java.io :as io]
5 | [clojure.string :as str]))
6 |
7 | (def version-file (io/file "resources" "clj-easy" "graal-build-time-version.txt"))
8 |
9 | (case (first *command-line-args*)
10 | (let [version-string (str/trim (slurp version-file))
11 | [major minor patch] (str/split version-string #"\.")
12 | new-version (str/join "." [major minor (inc (Integer/parseInt patch))])]
13 | (spit version-file new-version)))
14 |
--------------------------------------------------------------------------------
/scripts/lint.clj:
--------------------------------------------------------------------------------
1 | (ns lint
2 | (:require [babashka.classpath :as bbcp]
3 | [babashka.cli :as cli]
4 | [babashka.fs :as fs]
5 | [babashka.tasks :as t]
6 | [clojure.string :as string]
7 | [lread.status-line :as status]))
8 |
9 | (def clj-kondo-cache ".clj-kondo/.cache")
10 |
11 | (defn- cache-exists? []
12 | (fs/exists? clj-kondo-cache))
13 |
14 | (defn- delete-cache []
15 | (when (cache-exists?)
16 | (fs/delete-tree clj-kondo-cache)))
17 |
18 | (defn- build-cache []
19 | (when (cache-exists?)
20 | (delete-cache))
21 | (let [clj-cp (-> (t/clojure {:out :string}
22 | "-Spath -M:test:build")
23 | with-out-str
24 | string/trim)
25 | bb-cp (bbcp/get-classpath)]
26 | (status/line :detail "- copying lib configs and creating cache")
27 | (t/clojure "-M:clj-kondo --skip-lint --copy-configs --dependencies --parallel --lint" clj-cp bb-cp)))
28 |
29 | (defn- check-cache [{:keys [rebuild]}]
30 | (status/line :head "clj-kondo: cache check")
31 | (if-let [rebuild-reason (cond
32 | rebuild
33 | "Rebuild requested"
34 |
35 | (not (cache-exists?))
36 | "Cache not found"
37 |
38 | :else
39 | (let [updated-dep-files (fs/modified-since clj-kondo-cache ["deps.edn" "bb.edn"])]
40 | (when (seq updated-dep-files)
41 | (format "Found deps files newer than lint cache: %s" (mapv str updated-dep-files)))))]
42 | (do (status/line :detail rebuild-reason)
43 | (build-cache))
44 | (status/line :detail "Using existing cache")))
45 |
46 | (defn- lint [opts]
47 | (check-cache opts)
48 | (status/line :head "clj-kondo: linting")
49 | (let [{:keys [exit]}
50 | (t/clojure {:continue true}
51 | "-M:clj-kondo --parallel --lint src test scripts build.clj build_shared.clj deps.edn bb.edn")]
52 | (cond
53 | (= 2 exit) (status/die exit "clj-kondo found one or more lint errors")
54 | (= 3 exit) (status/die exit "clj-kondo found one or more lint warnings")
55 | (> exit 0) (status/die exit "clj-kondo returned unexpected exit code"))))
56 |
57 | (defn -main [& args]
58 | (when-let [opts (cli/parse-opts args)]
59 | (lint opts)))
60 |
61 | (when (= *file* (System/getProperty "babashka.file"))
62 | (apply -main *command-line-args*))
63 |
--------------------------------------------------------------------------------
/src/clj_easy/graal_build_time/InitClojureClasses.java:
--------------------------------------------------------------------------------
1 | package clj_easy.graal_build_time;
2 |
3 | import java.util.List;
4 | import java.nio.file.Path;
5 |
6 | import org.graalvm.nativeimage.hosted.Feature;
7 | import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
8 |
9 | public class InitClojureClasses implements Feature {
10 |
11 | @Override
12 | public void duringSetup(DuringSetupAccess access) {
13 | List classPath = access.getApplicationClassPath();
14 | String[] packages = clj_easy.graal_build_time.packages.list(classPath);
15 | String packagesStr = clj_easy.graal_build_time.packages.listStr(packages);
16 | System.out.println("[clj-easy/graal-build-time] Registering packages for build time initialization: " + packagesStr);
17 | RuntimeClassInitialization.initializeAtBuildTime(packages);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/clj_easy/graal_build_time/packages.clj:
--------------------------------------------------------------------------------
1 | (ns ^:no-doc clj-easy.graal-build-time.packages
2 | (:require [clojure.string :as str])
3 | (:import [java.nio.file Path]
4 | [java.util.jar JarFile JarFile$JarFileEntry])
5 | (:gen-class
6 | :methods [^{:static true} [list [java.util.List] "[Ljava.lang.String;"]
7 | ^{:static true} [listStr ["[Ljava.lang.String;"] String]]))
8 |
9 | (def ^:private jar-entry-file-separator "/")
10 |
11 | (defn ^:private entry->package [nm split]
12 | (let [package (->> (str/split nm (re-pattern (str/re-quote-replacement split)))
13 | drop-last
14 | (str/join "."))]
15 | (when (str/blank? package)
16 | (println (str "[clj-easy/graal-build-time] WARN: Single segment namespace found for class: " nm ". "
17 | "Because this class has no package, it cannot be registered for initialization at build time.")))
18 | package))
19 |
20 | (defn ^:private consider-entry? [nm file-sep]
21 | (and (not (str/starts-with? nm (str "clojure" file-sep)))
22 | (str/ends-with? nm "__init.class")))
23 |
24 | (defn ^:private contains-parent? [packages package]
25 | (some #(and (not= % package)
26 | (str/starts-with? (str package ".") (str % ".")))
27 | packages))
28 |
29 | (defn ^:private unique-packages [packages]
30 | (->> packages
31 | (remove (partial contains-parent? packages))
32 | set))
33 |
34 | (defn ^:private packages-from-jar
35 | [^Path jar-file]
36 | (with-open [jar (JarFile. (.toFile jar-file))]
37 | (let [entries (enumeration-seq (.entries jar))
38 | packages (->> entries
39 | (map #(.getName ^JarFile$JarFileEntry %))
40 | (filter #(consider-entry? % jar-entry-file-separator))
41 | (map #(entry->package % jar-entry-file-separator))
42 | (remove str/blank?)
43 | vec)]
44 | packages)))
45 |
46 | (defn ^:private packages-from-dir [^Path dir]
47 | (let [f (.toFile dir)
48 | files (rest (file-seq f))
49 | relatives (map (fn [^java.io.File f]
50 | (let [path (.toPath f)]
51 | (.relativize dir path)))
52 | files)
53 | names (map str relatives)
54 | packages (->> names
55 | (filter #(consider-entry? % (System/getProperty "file.separator")))
56 | (map #(entry->package % (System/getProperty "file.separator")))
57 | (remove str/blank?))]
58 | packages))
59 |
60 | (defn -list [paths]
61 | (->> paths
62 | (mapcat (fn [path]
63 | (if (str/ends-with? (str path) ".jar")
64 | (packages-from-jar path)
65 | (packages-from-dir path))))
66 | unique-packages
67 | sort
68 | (cons "clojure")
69 | distinct
70 | into-array))
71 |
72 | (defn -listStr [pl]
73 | (str/join ", " pl))
74 |
--------------------------------------------------------------------------------
/test-hello-world/bb.edn:
--------------------------------------------------------------------------------
1 | {:tasks
2 | {:requires ([babashka.fs :as fs]
3 | [babashka.tasks :as tasks]
4 | [clojure.string :as str])
5 | :enter (when (not (str/starts-with? (:name (current-task)) "-"))
6 | (println (format "-[hello-world %s]----" (:name (current-task)))))
7 | :init (do
8 | (defn clojure
9 | "Clojure defaulting to stderr output"
10 | [arg & args]
11 | (apply tasks/clojure (str "-J-Dclojure.main.report=stderr " arg) args)))
12 |
13 | -test-libs ["lib1" "lib2"]
14 | -artifact "target/hello-world.jar"
15 | -native-image-path "target"
16 |
17 | clean
18 | {:depends [-test-libs]
19 | :task (do
20 | (doseq [dir ["target" ".cpcache"]]
21 | (fs/delete-tree dir))
22 | (doseq [p -test-libs]
23 | (shell {:dir p} "bb clean")))}
24 |
25 | libs
26 | {:doc "Build test libs"
27 | :depends [-test-libs]
28 | :task (doseq [p -test-libs]
29 | (shell {:dir p} "bb build"))}
30 |
31 | graal-build-time-jar
32 | {:doc "Build and grab a copy of local graal build time jar from above."
33 | :task (do
34 | (shell {:dir ".."} "bb jar")
35 | (let [jar-name (-> (shell {:out :string :dir ".."}
36 | "bb jar-name")
37 | :out
38 | str/split-lines
39 | last)]
40 | (println "copying" jar-name)
41 | (fs/create-dirs "target")
42 | (fs/copy (str "../" jar-name)
43 | "target/graal-build-time.jar"
44 | {:replace-existing true})))}
45 |
46 | build {:doc "Build classes and uberjar"
47 | :depends [libs
48 | -artifact
49 | graal-build-time-jar]
50 | :task
51 | (if (seq (fs/modified-since -artifact ["../lib1/target/lib1.jar"
52 | "../lib2/target/lib2.jar"
53 | "src"
54 | "bb.edn"
55 | "deps.edn"
56 | "build.clj"]))
57 | (do
58 | (println "Building" -artifact)
59 | (clojure "-T:build uber"))
60 | (println -artifact "already built"))}
61 |
62 | graalvm {:doc "Checks GRAALVM_HOME env var"
63 | :task
64 | (let [env (System/getenv "GRAALVM_HOME")]
65 | (assert env "Set GRAALVM_HOME")
66 | env)}
67 |
68 | -graalvm-native-image-exe
69 | {:doc "Resolves and returns graalvm native-image binary"
70 | :task (when-let [native-image (if-let [ghome (System/getenv "GRAALVM_HOME")]
71 | (or (fs/which (fs/file ghome "bin" "native-image"))
72 | (throw (ex-info "Could not find GraalVM native-image via GRAALVM_HOME." {})))
73 | (or (fs/which "native-image")
74 | (throw (ex-info "GRAALVM_HOME not set, and did not find GraalVM native-image on PATH" {}))))]
75 | (println "Using GraalVM native-image:" (str native-image))
76 | (shell native-image "--version")
77 | native-image)}
78 |
79 | ;;
80 | ;; native image from uber
81 | ;;
82 | -native-image-uber-name
83 | "hello-world-uber"
84 |
85 | native-image-uber
86 | {:doc "Builds native image from uber"
87 | :depends [build
88 | -graalvm-native-image-exe
89 | -native-image-path
90 | -native-image-uber-name]
91 | :task (let [exe (str (fs/file -native-image-path -native-image-uber-name))
92 | full-exe (str (fs/which exe))]
93 | (if (seq (fs/modified-since full-exe ["target/hello-world.jar"]))
94 | (do (println "Building" exe)
95 | (shell -graalvm-native-image-exe
96 | ;; note: we are omitting --initialize-at-build-time
97 | "-jar" "target/hello-world.jar"
98 | "--features=clj_easy.graal_build_time.InitClojureClasses"
99 | "-O1" ;; basic optimization for faster build
100 | "--no-fallback"
101 | "--verbose"
102 | "-o" exe))
103 | (println "Already built" full-exe)))}
104 |
105 | native-image-uber-test
106 | {:doc "Run native image built from uber"
107 | :depends [native-image-uber
108 | -native-image-path
109 | -native-image-uber-name]
110 | :task (let [full-exe (str (fs/which (fs/file -native-image-path -native-image-uber-name)))]
111 | (println "Running" full-exe)
112 | (shell full-exe))}
113 |
114 | ;;
115 | ;; native image from classes
116 | ;;
117 | -native-image-classes-name
118 | "hello-world-classes"
119 |
120 | native-image-classes
121 | {:doc "Builds native image from classes"
122 | :depends [build
123 | -graalvm-native-image-exe
124 | -native-image-path
125 | -native-image-classes-name]
126 | :task
127 | (let [exe (str (fs/file -native-image-path -native-image-classes-name))
128 | full-exe (str (fs/which exe))]
129 | (if (seq (fs/modified-since exe "target/classes"))
130 | (do (println "Building" exe)
131 | (shell -graalvm-native-image-exe
132 | ;; note: we are omitting --initialize-at-build-time
133 | "-cp" (str "target/classes"
134 | (System/getProperty "path.separator")
135 | (-> (with-out-str (clojure "-Spath")) str/trim))
136 | "--features=clj_easy.graal_build_time.InitClojureClasses"
137 | "-O1" ;; basic optimization for faster build
138 | "--no-fallback"
139 | "--verbose"
140 | "-o" exe
141 | "hello_world.main")))
142 | (println "Already built" full-exe))}
143 |
144 | native-image-classes-test
145 | {:doc "Run native image built from classes"
146 | :depends [native-image-classes
147 | -native-image-path
148 | -native-image-classes-name]
149 | :task (let [full-exe (str (fs/which (fs/file -native-image-path -native-image-classes-name)))]
150 | (println "Running" full-exe)
151 | (shell full-exe))}
152 |
153 | ;;
154 | ;; run all native image tests
155 | ;;
156 | native-image-test
157 | {:doc "Run integration tests"
158 | :depends [native-image-uber-test native-image-classes-test]
159 | :task (println "Done.")}}}
160 |
--------------------------------------------------------------------------------
/test-hello-world/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:require [build-helper.core :as bh]))
3 |
4 | (defn uber [_]
5 | (bh/uber {:lib 'hello-world/hello-world
6 | :main 'hello-world.main
7 | :uber-file "target/hello-world.jar"}))
8 |
--------------------------------------------------------------------------------
/test-hello-world/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src"]
2 | :deps {org.clojure/clojure {:mvn/version "1.12.0"}
3 | lib1/lib1 {:local/root "lib1/target/lib1.jar"}
4 | lib2/lib2 {:local/root "lib2/target/lib2.jar"}
5 | clj-easy/graal-build-time {:local/root "target/graal-build-time.jar"}}
6 | :aliases {:build {:deps {clj-easy/build-helper {:local/root "../build-helper"}}
7 | :ns-default build}}}
8 |
--------------------------------------------------------------------------------
/test-hello-world/lib1/bb.edn:
--------------------------------------------------------------------------------
1 | {:tasks
2 | {:requires ([babashka.fs :as fs]
3 | [clojure.string :as str])
4 | :enter (when (not (str/starts-with? (:name (current-task)) "-"))
5 | (println (format "-[lib1 %s]----" (:name (current-task)))))
6 |
7 | clean (doseq [dir ["target" ".cpcache"]]
8 | (fs/delete-tree dir))
9 | -artifact "target/lib1.jar"
10 | build {:depends [-artifact]
11 | :task
12 | (if (seq (fs/modified-since -artifact ["src" "bb.edn" "deps.edn" "build.clj"]))
13 | (do (println "Building" -artifact)
14 | (clojure "-T:build jar"))
15 | (println -artifact "already built"))}}}
16 |
--------------------------------------------------------------------------------
/test-hello-world/lib1/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:require [build-helper.core :as bh]))
3 |
4 | (defn jar [_]
5 | (bh/jar {:lib 'gbt-test-org/lib1
6 | :jar-file "target/lib1.jar"}))
7 |
--------------------------------------------------------------------------------
/test-hello-world/lib1/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src"]
2 | :aliases {:build {:deps {clj-easy/build-helper {:local/root "../../build-helper"}}
3 | :ns-default build}}}
4 |
--------------------------------------------------------------------------------
/test-hello-world/lib1/src/gbt_test_org/p2/core.clj:
--------------------------------------------------------------------------------
1 | (ns gbt-test-org.p2.core
2 | "This package is a child package of a parent package that occurs in lib2.
3 | It should not therefore not be included in build init registration.")
4 |
5 | (defn dummy [])
6 |
--------------------------------------------------------------------------------
/test-hello-world/lib2/bb.edn:
--------------------------------------------------------------------------------
1 | {:tasks
2 | {:requires ([babashka.fs :as fs]
3 | [clojure.string :as str])
4 | :enter (when (not (str/starts-with? (:name (current-task)) "-"))
5 | (println (format "-[lib2 %s]----" (:name (current-task)))))
6 |
7 | clean (doseq [dir ["target" ".cpcache"]]
8 | (fs/delete-tree dir))
9 | -artifact "target/lib2.jar"
10 | build {:depends [-artifact]
11 | :task
12 | (if (seq (fs/modified-since -artifact ["src" "bb.edn" "deps.edn" "build.clj"]))
13 | (do (println "Building" -artifact)
14 | (clojure "-T:build jar"))
15 | (println -artifact "already built"))}}}
16 |
--------------------------------------------------------------------------------
/test-hello-world/lib2/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:require [build-helper.core :as bh]))
3 |
4 | (defn jar [_]
5 | (bh/jar {:lib 'gbt-test-org/lib2
6 | :jar-file "target/lib2.jar"}))
7 |
--------------------------------------------------------------------------------
/test-hello-world/lib2/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src"]
2 | :aliases {:build {:deps {clj-easy/build-helper {:local/root "../../build-helper"}}
3 | :ns-default build}}}
4 |
--------------------------------------------------------------------------------
/test-hello-world/lib2/src/gbt_test_org/core.clj:
--------------------------------------------------------------------------------
1 | (ns gbt-test-org.core
2 | "This package should be included in build init registration.")
3 |
4 | (defn dummy [])
5 |
--------------------------------------------------------------------------------
/test-hello-world/src/hello/core.clj:
--------------------------------------------------------------------------------
1 | (ns hello.core
2 | "This package will be included in build init registration.
3 | It is included to test that:
4 | hello_world is not excluded because it happens to start with this package's name:
5 | hello")
6 |
7 | (defn dummy [])
8 |
--------------------------------------------------------------------------------
/test-hello-world/src/hello_world/main.clj:
--------------------------------------------------------------------------------
1 | (ns hello-world.main
2 | (:require [hello.core]
3 | [gbt-test-org.core]
4 | [gbt-test-org.p2.core])
5 | (:gen-class))
6 |
7 | (defn -main []
8 | ;; sanity for test nses loaded and included
9 | (hello.core/dummy)
10 | (gbt-test-org.core/dummy)
11 | (gbt-test-org.p2.core/dummy)
12 | (println "Hello world"))
13 |
--------------------------------------------------------------------------------
/test-single-segment/bb.edn:
--------------------------------------------------------------------------------
1 | {:tasks
2 | {:requires ([babashka.fs :as fs]
3 | [babashka.tasks :as tasks]
4 | [clojure.string :as str])
5 | :enter (when (not (str/starts-with? (:name (current-task)) "-"))
6 | (println (format "-[test-single-segment %s]----" (:name (current-task)))))
7 | :init (do
8 | (defn clojure
9 | "Clojure defaulting to stderr output"
10 | [arg & args]
11 | (apply tasks/clojure (str "-J-Dclojure.main.report=stderr " arg) args)))
12 |
13 | -artifact "target/single-segment.jar"
14 |
15 | clean
16 | {:task (doseq [dir ["target" ".cpcache"]]
17 | (fs/delete-tree dir))}
18 |
19 | graal-build-time-jar
20 | {:doc "Build and grab a copy of local graal build time jar from above."
21 | :task (do
22 | (shell {:dir ".."} "bb jar")
23 | (let [jar-name (-> (shell {:out :string :dir ".."}
24 | "bb jar-name")
25 | :out
26 | str/split-lines
27 | last)]
28 | (println "copying" jar-name)
29 | (fs/create-dirs "target")
30 | (fs/copy (str "../" jar-name)
31 | "target/graal-build-time.jar"
32 | {:replace-existing true})))}
33 |
34 | build {:doc "Build classes and uberjar"
35 | :depends [-artifact
36 | graal-build-time-jar]
37 | :task
38 | (if (seq (fs/modified-since -artifact ["src"
39 | "bb.edn"
40 | "deps.edn"
41 | "build.clj"]))
42 | (do
43 | (println "Building" -artifact)
44 | (clojure "-T:build uber"))
45 | (println -artifact "already built"))}}}
46 |
--------------------------------------------------------------------------------
/test-single-segment/build.clj:
--------------------------------------------------------------------------------
1 | (ns build
2 | (:require [build-helper.core :as bh]))
3 |
4 | (defn uber [_]
5 | (bh/uber {:lib 'single-segment/single-segment
6 | :main 'single-segment.main
7 | :uber-file "target/single-segment.jar"}))
8 |
--------------------------------------------------------------------------------
/test-single-segment/deps.edn:
--------------------------------------------------------------------------------
1 | {:deps {org.clojure/clojure {:mvn/version "1.12.0"}
2 | org.clj-commons/digest {:mvn/version "1.4.100"}
3 | clj-easy/graal-build-time {:local/root "target/graal-build-time.jar"}}
4 | :aliases {:build {:deps {clj-easy/build-helper {:local/root "../build-helper"}}
5 | :ns-default build}}}
6 |
--------------------------------------------------------------------------------
/test-single-segment/src/single_segment/main.clj:
--------------------------------------------------------------------------------
1 | (ns single-segment.main
2 | "Exists to test our emitting of warning message for single segement namespaces"
3 | (:require [digest]) ;; this deprecated ns is, as you can see, single segment
4 | (:gen-class))
5 |
6 | (defn -main []
7 | (println "md5 digest for clojure" (digest/md5 "clojure")))
8 |
--------------------------------------------------------------------------------
/test/clj_easy/graal_build_time/packages_test.clj:
--------------------------------------------------------------------------------
1 | (ns clj-easy.graal-build-time.packages-test
2 | (:require [babashka.process :as p]
3 | [clj-easy.graal-build-time.packages :as packages]
4 | [clojure.java.io :as io]
5 | [clojure.test :refer [deftest is testing]]))
6 |
7 | (deftest hello-world-package-list-test
8 | (-> (p/process ["bb" "build-hello-world"] {:inherit true})
9 | (p/check))
10 | (let [expected-packages "clojure, clj_easy.graal_build_time, gbt_test_org, hello, hello_world"]
11 | (testing "packages from directory"
12 | (is (= expected-packages
13 | (-> (packages/-list (->> ["test-hello-world/target/classes"
14 | "test-hello-world/lib1/target/lib1.jar"
15 | "test-hello-world/lib2/target/lib2.jar"
16 | "test-hello-world/target/graal-build-time.jar"]
17 | (mapv #(.toPath (io/file %)))))
18 | (packages/-listStr)))))
19 | (testing "packages from jar"
20 | (is (= expected-packages
21 | (-> (packages/-list [(.toPath (io/file "test-hello-world/target/hello-world.jar"))])
22 | (packages/-listStr)))))))
23 |
24 | (deftest single-segment-warning-message-test
25 | (-> (p/process ["bb" "build-single-segment"] {:inherit true})
26 | (p/check))
27 | (let [expected-packages "clojure, clj_easy.graal_build_time, single_segment"
28 | expected-warning #"^\[clj-easy/graal-build-time\] WARN: Single segment .* digest__init.class"]
29 | (testing "packages from directory"
30 | (is (re-find
31 | expected-warning
32 | (with-out-str
33 | (is (= expected-packages
34 | (-> (packages/-list (->> ["test-single-segment/target/classes"
35 | "test-single-segment/target/graal-build-time.jar"]
36 | (mapv #(.toPath (io/file %)))))
37 | (packages/-listStr))))))))
38 | (testing "packages from jar"
39 | (is (re-find
40 | expected-warning
41 | (with-out-str
42 | (is (= expected-packages
43 | (-> (packages/-list [(.toPath (io/file "test-single-segment/target/single-segment.jar"))])
44 | (packages/-listStr))))))))))
45 |
--------------------------------------------------------------------------------