-
Notifications
You must be signed in to change notification settings - Fork 1
Variant: Native image #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3a5682e
deaffce
671ebdc
e098946
6b51977
78999b5
b71cdd4
de4470b
36d9bc7
121b906
6176080
1a354b5
c00fa4b
5108de3
8cd6dc2
53b946b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| (ns native-image | ||
| (:require [babashka.fs :as fs] | ||
| [babashka.http-client :as http] | ||
| [babashka.process :as p] | ||
| [babashka.wait :as wait] | ||
| [cheshire.core :as json] | ||
| [clojure.string :as str] | ||
| [clojure.walk :as walk])) | ||
|
|
||
| (defn start-backend [{:keys [http-port extra-env timeout-ms command]}] | ||
| (p/shell "bb db-up") | ||
| (let [command (if (string? command) | ||
| [command] | ||
| command) | ||
| backend-process (apply p/process | ||
| {:inherit true | ||
| :extra-env extra-env} | ||
| command)] | ||
| (when (= :timeout (wait/wait-for-port "localhost" http-port | ||
| {:timeout timeout-ms | ||
| :default :timeout})) | ||
| (p/destroy-tree backend-process) | ||
| (throw (Exception. (str "Backend didn't start in " timeout-ms "ms")))) | ||
| backend-process)) | ||
|
|
||
| (defn run-training [http-port] | ||
| (let [base-url (format "http://localhost:%s" http-port) | ||
| test-requests [;; Database access | ||
| [200 (str base-url "/api/todo")] | ||
| ;; Buddy | ||
| [200 (str base-url "/api/buddy-test")]]] | ||
| (doall (keep (fn [[expected-status url]] | ||
| (let [response (http/get url {:throw false})] | ||
| (when (not= expected-status (:status response)) | ||
| response))) | ||
| test-requests)))) | ||
|
|
||
| (defn check-failures [failures] | ||
| (when (not (zero? (count failures))) | ||
| (println "Training requests failed:") | ||
| (doseq [failure failures] | ||
| (prn failure)) | ||
| (System/exit 1))) | ||
|
|
||
| (defn remove-clojure-name [coll clojure-names keeps] | ||
| (remove (fn [{:keys [type glob]}] | ||
| (let [s (or glob | ||
| (when (and type | ||
| (string? type)) | ||
| type))] | ||
| (if s | ||
| (some #(and (or (.startsWith s %) | ||
| (.contains s %)) | ||
| (not (some (fn [k] | ||
| (.contains % k)) | ||
| keeps))) | ||
| clojure-names) | ||
| false))) | ||
| coll)) | ||
|
|
||
| (defn post-process-metadata | ||
| "Removes Clojure relates entries from the metadata file, idea is to keep only entries from dependencies | ||
|
|
||
| Apparently, keeping Clojure entries leads into problems with code that modifies dynamic variables such as *warn-on-reflection*" | ||
| [filename] | ||
| (fs/copy filename (str filename ".orig")) | ||
| (let [data (json/parse-string (slurp filename) true) | ||
| clojure-names-atom (atom #{"clojure"})] | ||
| (walk/postwalk (fn [o] | ||
| (when (and (string? o) | ||
| (or (.endsWith o ".clj") | ||
| (.endsWith o ".cljc"))) | ||
| (let [clojure-name (str/replace o | ||
| (if (.endsWith o ".clj") | ||
| ".clj" | ||
| ".cljc") | ||
| "")] | ||
| (swap! clojure-names-atom conj clojure-name (str/replace clojure-name "/" ".")))) | ||
| o) | ||
| data) | ||
| (let [new-data (-> data | ||
| (update :reflection #(remove-clojure-name % @clojure-names-atom #{"jetty9"})) ;; To support ring-jetty9-adapter | ||
| (update :resources #(remove-clojure-name % @clojure-names-atom #{})) | ||
| (update :jni #(remove-clojure-name % @clojure-names-atom #{})))] | ||
| (spit filename (json/generate-string new-data {:pretty true}))))) | ||
|
|
||
| ;; Do a training run to gather metadata via native-image-agent | ||
| ;; Note that should not refer to any unnecessary test resources on the classpath during training, | ||
| ;; since those will get included into the metadata | ||
| (defn run-with-agent [] | ||
| (let [http-port 3322 | ||
| tracing-process (start-backend {:http-port http-port | ||
| :extra-env {"CONFIG_EDN" "resources/config.edn" | ||
| "HTTP_PORT" http-port} | ||
| :timeout-ms 5000 | ||
| :command ["java" (str "-agentlib:native-image-agent=" | ||
| "config-output-dir=target/native-image-configuration/META-INF/native-image") | ||
| "-jar" "target/app.jar"]}) | ||
| failures (run-training http-port)] | ||
| (p/destroy-tree tracing-process) | ||
| (check-failures failures) | ||
| ;; Wait for native-image-agent to create the tracing file | ||
| (when (= :timeout (wait/wait-for-path "target/native-image-configuration/META-INF/native-image/reachability-metadata.json" | ||
| {:timeout 5000 | ||
| :default :timeout})) | ||
| (println "Reachability metadata file not created") | ||
| (System/exit 1))) | ||
|
|
||
| (post-process-metadata "target/native-image-configuration/META-INF/native-image/reachability-metadata.json")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,25 @@ | ||
| (ns backend.main | ||
| (:require [aero.core :as aero] | ||
| [backend.routes] | ||
| [clojure.java.io :as io] | ||
| [clojure.tools.logging :as log] | ||
| [cognitect.transit] | ||
| [hikari-cp.core :as hikari-cp] | ||
| [integrant.core :as ig] | ||
| [next.jdbc.date-time] | ||
| [reitit.ring.middleware.exception] | ||
| [ring.adapter.jetty :as jetty])) | ||
| [ring.adapter.jetty9 :as jetty] | ||
| #_[ring.adapter.jetty :as jetty]) | ||
| (:gen-class)) | ||
|
|
||
| (set! *warn-on-reflection* true) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it is good idea to set this in any ns that does any JVM interop. I wonder if there is a way to enable this for the whole project, or is that something we would want. |
||
|
|
||
| (defmethod aero.core/reader 'ig/ref | ||
| [_opts _tag value] | ||
| (ig/ref value)) | ||
|
|
||
| (defn system-config [] | ||
| (aero/read-config (io/resource "config.edn"))) | ||
| (aero/read-config (or (System/getenv "CONFIG_EDN") | ||
| "config.edn"))) | ||
|
Comment on lines
+21
to
+22
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think adding the env var here does make sense, but AFAIK removing the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think this might be a mistake even, was trying to avoid putting the config.edn into the resulting native image, since classpath resource loading is captured via the agent run, but here the config is actually a aero config, which just bundles defaults and instructs reading from environment variables, so we actually want the file in the native binary too... Though, I think in the past I've found use in being able to specify alternative aero config.edn, that can be loaded outside the classpath, but maybe this is not useful in an example template. |
||
|
|
||
| (defmethod ig/init-key :adapter/jetty [_ {:keys [port routes] :as jetty-opts}] | ||
| (log/infof "Starting Jetty server on http://localhost:%s" port) | ||
|
|
@@ -48,5 +52,8 @@ | |
| (catch Throwable t | ||
| (log/error t "Failed to start system")))) | ||
|
|
||
| (defn -main [] | ||
| (run-system (system-config))) | ||
| (defn -main [& args] | ||
| (run-system (system-config)) | ||
| (when (= "exit-after-start" (first args)) | ||
| (log/info "Exiting") | ||
| (System/exit 0))) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of disagree with including user specific REPL tools in the project configuration.
Usually, cider-nrepl middleware is added by the editor when launching the REPL process. With lein tools like hashp were easy to add to the
~/.lein/profiles.cljbut I guess it isn't as easy with~/.clojure/deps.ednbecause you'd still need to refer to a alias created there to enable those deps... hmm.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, agree too, was just for my own convenience here.
Use specific aliases need to be activated, so if a project has say a
bb devtask bundled for easy startup, maybe there could be options in the task to specify those aliases, or actually, I wonder if babashka supports user-specific task files, since this user-specific customization needs kind of two things 1) deps.edn 2) starting with custom arguments...