diff --git a/README.md b/README.md index eaef5adb..64728467 100644 --- a/README.md +++ b/README.md @@ -771,6 +771,8 @@ handling of the SIGPIPE. This can be done by setting - [Clojure in the Shell](https://lambdaisland.com/blog/2019-12-05-advent-of-parens-5-clojure-in-the-shell) by Arne Brasseur - [Clojure Tool](https://purelyfunctional.tv/issues/purelyfunctional-tv-newsletter-351-clojure-tool-babashka/) by Eric Normand +## [Building babashka](doc/build.md) + ## [Developing Babashka](doc/dev.md) ## Related projects diff --git a/deps.edn b/deps.edn index 70347df5..747b2866 100644 --- a/deps.edn +++ b/deps.edn @@ -13,7 +13,8 @@ clj-commons/clj-yaml {:mvn/version "0.7.1"} com.cognitect/transit-clj {:mvn/version "1.0.324"} seancorfield/next.jdbc {:mvn/version "1.0.424"} - org.postgresql/postgresql {:mvn/version "42.2.12"}} + org.postgresql/postgresql {:mvn/version "42.2.12"} + org.hsqldb/hsqldb {:mvn/version "2.4.0"}} :aliases {:main {:main-opts ["-m" "babashka.main"]} :profile diff --git a/doc/build.md b/doc/build.md new file mode 100644 index 00000000..f10e9ffb --- /dev/null +++ b/doc/build.md @@ -0,0 +1,77 @@ +# Building babashka + +## Prerequisites + +- Install [lein](https://leiningen.org/) for producing uberjars +- Download [GraalVM](https://www.graalvm.org/downloads/). Currently we use *java8-19.3.1*. +- Set `$GRAALVM_HOME` to the GraalVM distribution directory. On macOS this can look like: + + ``` shell + export GRAALVM_HOME=~/Downloads/graalvm-ce-java8-19.3.1/Contents/Home + ``` + + On linux: + + ``` shell + export GRAALVM_HOME=~/Downloads/graalvm-ce-java8-19.3.1 + ``` + + On Windows: + ``` + set GRAALVM_HOME=C:\Users\IEUser\Downloads\graalvm-ce-java8-19.3.1 + ``` + +## Clone repository + +NOTE: the babashka repository contains submodules. You need to use the +`--recursive` flag to clone these submodules along with the main repo. + +``` shellsession +$ git clone https://github.com/borkdude/babashka --recursive +``` + +To update later on: + +``` shellsession +$ git submodule update --recursive +``` + +## Build + +Run the `compile` script: + +``` shell +$ script/compile +``` + +To configure maximum heap size you can use: + +``` +$ BABASHKA_XMX="-J-Xmx4g" script/compile +``` + +## Windows + +To compile on Windows you need to check out the `windows` branch: + +``` shell +$ git checkout windows +``` + +The compile script for Windows is `script/compile.bat`. + +## Optional features + +### HyperSQL + +To compile babashka with `HyperSQL`/`hsqldb` support, set +`BABASHKA_FEATURE_HSQLDB` to `true`: + + +``` shell +$ BABASHKA_FEATURE_HSQLDB=true script/compile +``` + +If you think this feature should be enabled in the distributed version of `bb`, +vote with a thumbs up on [this](https://github.com/borkdude/babashka/issues/382) +issue. diff --git a/doc/dev.md b/doc/dev.md index 192e02e4..487fec87 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -45,17 +45,7 @@ Test the native version: ## Build -To build this project, set `$GRAALVM_HOME` to the GraalVM distribution directory. Currently we are using GraalVM JDK8. - -Then run: - - $ script/compile - -To tweak maximum heap size: - -``` -$ BABASHKA_XMX="-J-Xmx4g" script/compile -``` +See [build.md](build.md). ## Binary size diff --git a/doc/examples.md b/doc/examples.md index a42ece92..d4f5a1ea 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -272,3 +272,25 @@ A script to retrieve the version from a `pom.xml` file. See Show frequencies of messages by user in Whatsapp group chats. See [examples/whatsapp_frequencies.clj](examples/whatsapp_frequencies.clj) + +### Find unused vars + +[This](../examples/hsqldb_unused_vars.clj) script invokes clj-kondo, stores +returned data in an in memory HSQLDB database and prints the result of a query +which finds unused vars. + +``` shell +$ bb examples/hsqldb_unused_vars.clj src + +| :VARS/NS | :VARS/NAME | :VARS/FILENAME | :VARS/ROW | :VARS/COL | +|----------------------------+--------------------------+------------------------------------+-----------+-----------| +| babashka.impl.bencode.core | read-netstring | src/babashka/impl/bencode/core.clj | 162 | 1 | +| babashka.impl.bencode.core | write-netstring | src/babashka/impl/bencode/core.clj | 201 | 1 | +| babashka.impl.classes | generate-reflection-file | src/babashka/impl/classes.clj | 230 | 1 | +| babashka.impl.classpath | ->DirectoryResolver | src/babashka/impl/classpath.clj | 12 | 1 | +| babashka.impl.classpath | ->JarFileResolver | src/babashka/impl/classpath.clj | 37 | 1 | +| babashka.impl.classpath | ->Loader | src/babashka/impl/classpath.clj | 47 | 1 | +| babashka.impl.clojure.test | file-position | src/babashka/impl/clojure/test.clj | 286 | 1 | +| babashka.impl.nrepl-server | stop-server! | src/babashka/impl/nrepl_server.clj | 179 | 1 | +| babashka.main | -main | src/babashka/main.clj | 485 | 1 | +``` diff --git a/examples/hsqldb_unused_vars.clj b/examples/hsqldb_unused_vars.clj new file mode 100755 index 00000000..ebb94c86 --- /dev/null +++ b/examples/hsqldb_unused_vars.clj @@ -0,0 +1,85 @@ +#!/usr/bin/env bb + +;; $ examples/hsqldb_unused_vars.clj src + +;; | :VARS/NS | :VARS/NAME | :VARS/FILENAME | :VARS/ROW | :VARS/COL | +;; |----------------------------+--------------------------+------------------------------------+-----------+-----------| +;; | babashka.impl.bencode.core | read-netstring | src/babashka/impl/bencode/core.clj | 162 | 1 | +;; | babashka.impl.bencode.core | write-netstring | src/babashka/impl/bencode/core.clj | 201 | 1 | +;; | babashka.impl.classes | generate-reflection-file | src/babashka/impl/classes.clj | 230 | 1 | +;; | babashka.impl.classpath | ->DirectoryResolver | src/babashka/impl/classpath.clj | 12 | 1 | +;; | babashka.impl.classpath | ->JarFileResolver | src/babashka/impl/classpath.clj | 37 | 1 | +;; | babashka.impl.classpath | ->Loader | src/babashka/impl/classpath.clj | 47 | 1 | +;; | babashka.impl.clojure.test | file-position | src/babashka/impl/clojure/test.clj | 286 | 1 | +;; | babashka.impl.nrepl-server | stop-server! | src/babashka/impl/nrepl_server.clj | 179 | 1 | +;; | babashka.main | -main | src/babashka/main.clj | 485 | 1 | + +(ns hsqldb-unused-vars + (:require + [clojure.edn :as edn] + [clojure.java.shell :refer [sh]] + [clojure.pprint :refer [print-table]] + [next.jdbc :as jdbc] + [next.jdbc.sql :as sql])) + +(def db "jdbc:hsqldb:mem:testdb;sql.syntax_mys=true") + +(defn query [q] + (jdbc/execute! db [q])) + +(defn create-db! [] + (query "create table vars ( + ns text + , name text + , filename text + , row int + , col int )") + (query "create table var_usages ( + \"from\" text + , \"to\" text + , name text + , filename text + , row int + , col int )")) + +(defn parse-int [^String x] + (when x + (Integer. x))) + +(defn insert-vars! [var-definitions] + (sql/insert-multi! db :vars [:ns :name :filename :row :col] + (map (juxt (comp str :ns) + (comp str :name) + :filename + (comp parse-int :row) + (comp parse-int :col)) + var-definitions))) + +(defn insert-var-usages! [var-usages] + (sql/insert-multi! db :var_usages ["\"from\"" "\"to\"" :name :filename :row :col] + (map (juxt (comp str :from) + (comp str :to) + (comp str :name) + :filename + (comp parse-int :row) + (comp parse-int :col)) + var-usages))) + +(defn analysis->db [paths] + (let [out (:out (apply sh "clj-kondo" + "--config" "{:output {:analysis true :format :edn}}" + "--lint" paths)) + analysis (:analysis (edn/read-string out)) + {:keys [:var-definitions :var-usages]} analysis] + (insert-vars! var-definitions) + (insert-var-usages! var-usages))) + +(create-db!) +(analysis->db *command-line-args*) + +(def unused-vars " +select distinct * from vars v +where (v.ns, v.name) not in +(select vu.\"to\", vu.name from var_usages vu)") + +(print-table (query unused-vars)) diff --git a/project.clj b/project.clj index 89e64b78..477d1aa9 100644 --- a/project.clj +++ b/project.clj @@ -26,7 +26,8 @@ [com.cognitect/transit-clj "1.0.324"] [seancorfield/next.jdbc "1.0.424"] [org.postgresql/postgresql "42.2.12"]] - :profiles {:test {:dependencies [[clj-commons/conch "0.9.2"] + :profiles {:feature/hsqldb {:dependencies [[org.hsqldb/hsqldb "2.4.0"]]} + :test {:dependencies [[clj-commons/conch "0.9.2"] [com.clojure-goes-fast/clj-async-profiler "0.4.1"]]} :uberjar {:global-vars {*assert* false} :jvm-opts ["-Dclojure.compiler.direct-linking=true" diff --git a/script/compile b/script/compile index ad89f4de..fb02b454 100755 --- a/script/compile +++ b/script/compile @@ -20,9 +20,17 @@ export JAVA_HOME=$GRAALVM_HOME SVM_JAR=$(find "$GRAALVM_HOME" | grep svm.jar) $GRAALVM_HOME/bin/javac -cp "$SVM_JAR" resources/CutOffCoreServicesDependencies.java + +BABASHKA_LEIN_PROFILES="" + +if [ "$BABASHKA_FEATURE_HSQLDB" = "true" ] +then + BABASHKA_LEIN_PROFILES="+feature/hsqldb" +fi + if [ -z "$BABASHKA_JAR" ]; then - lein with-profiles +reflection do run - lein do clean, uberjar + lein with-profiles "+reflection,$BABASHKA_LEIN_PROFILES" do run + lein with-profiles "$BABASHKA_LEIN_PROFILES" do clean, uberjar BABASHKA_JAR=${BABASHKA_JAR:-"target/babashka-$BABASHKA_VERSION-standalone.jar"} fi @@ -53,6 +61,10 @@ if [ "$BABASHKA_STATIC" = "true" ]; then args+=("--static") fi +if [ "$BABASHKA_FEATURE_HSQLDB" = "true" ]; then + args+=("-H:IncludeResources=org/hsqldb/.*\.properties", "-H:IncludeResources=org/hsqldb/.*\.sql") +fi + $GRAALVM_HOME/bin/native-image "${args[@]}" if [ ! -z "$(command -v lein)" ]; then diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index a2e9b66a..300ffe72 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -1,8 +1,86 @@ (ns babashka.impl.classes {:no-doc true} (:require + [babashka.impl.features :as features] [cheshire.core :as json])) +(def custom-map + (cond-> + `{clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true + :allPublicMethods true} + java.lang.Thread + {:allPublicConstructors true + ;; generated with `public-declared-method-names`, see in + ;; `comment` below + :methods [{:name "activeCount"} + {:name "checkAccess"} + {:name "currentThread"} + {:name "dumpStack"} + {:name "enumerate"} + {:name "getAllStackTraces"} + {:name "getContextClassLoader"} + {:name "getDefaultUncaughtExceptionHandler"} + {:name "getId"} + {:name "getName"} + {:name "getPriority"} + {:name "getStackTrace"} + {:name "getState"} + {:name "getThreadGroup"} + {:name "getUncaughtExceptionHandler"} + {:name "holdsLock"} + {:name "interrupt"} + {:name "interrupted"} + {:name "isAlive"} + {:name "isDaemon"} + {:name "isInterrupted"} + {:name "join"} + {:name "run"} + {:name "setContextClassLoader"} + {:name "setDaemon"} + {:name "setDefaultUncaughtExceptionHandler"} + {:name "setName"} + {:name "setPriority"} + {:name "setUncaughtExceptionHandler"} + {:name "sleep"} + {:name "start"} + {:name "toString"} + {:name "yield"}]} + java.net.URL + {:allPublicConstructors true + :allPublicFields true + ;; generated with `public-declared-method-names`, see in + ;; `comment` below + :methods [{:name "equals"} + {:name "getAuthority"} + {:name "getContent"} + {:name "getDefaultPort"} + {:name "getFile"} + {:name "getHost"} + {:name "getPath"} + {:name "getPort"} + {:name "getProtocol"} + {:name "getQuery"} + {:name "getRef"} + {:name "getUserInfo"} + {:name "hashCode"} + {:name "openConnection"} + {:name "openStream"} + {:name "sameFile"} + ;; not supported: {:name "setURLStreamHandlerFactory"} + {:name "toExternalForm"} + {:name "toString"} + {:name "toURI"}]} + com.sun.xml.internal.stream.XMLInputFactoryImpl + {:methods [{:name "" :parameterTypes []}]} + com.sun.xml.internal.stream.XMLOutputFactoryImpl + {:methods [{:name "" :parameterTypes []}]}} + features/hsqldb? (assoc `org.hsqldb.dbinfo.DatabaseInformationFull + {:methods [{:name "" + :parameterTypes ["org.hsqldb.Database"]}]} + `java.util.ResourceBundle + {:methods [{:name "getBundle" + :parameterTypes ["java.lang.String","java.util.Locale","java.lang.ClassLoader"]}]}))) + (def classes `{:all [clojure.lang.ExceptionInfo java.io.BufferedReader @@ -108,7 +186,7 @@ java.util.zip.GZIPOutputStream org.yaml.snakeyaml.error.YAMLException ~(symbol "[B") - ] + ~@(when features/hsqldb? [`org.hsqldb.jdbcDriver])] :constructors [clojure.lang.Delay clojure.lang.MapEntry clojure.lang.LineNumberingPushbackReader @@ -120,74 +198,7 @@ :fields [clojure.lang.PersistentQueue] :instance-checks [clojure.lang.IObj clojure.lang.IEditableCollection] - :custom {clojure.lang.LineNumberingPushbackReader {:allPublicConstructors true - :allPublicMethods true} - java.lang.Thread - {:allPublicConstructors true - ;; generated with `public-declared-method-names`, see in - ;; `comment` below - :methods [{:name "activeCount"} - {:name "checkAccess"} - {:name "currentThread"} - {:name "dumpStack"} - {:name "enumerate"} - {:name "getAllStackTraces"} - {:name "getContextClassLoader"} - {:name "getDefaultUncaughtExceptionHandler"} - {:name "getId"} - {:name "getName"} - {:name "getPriority"} - {:name "getStackTrace"} - {:name "getState"} - {:name "getThreadGroup"} - {:name "getUncaughtExceptionHandler"} - {:name "holdsLock"} - {:name "interrupt"} - {:name "interrupted"} - {:name "isAlive"} - {:name "isDaemon"} - {:name "isInterrupted"} - {:name "join"} - {:name "run"} - {:name "setContextClassLoader"} - {:name "setDaemon"} - {:name "setDefaultUncaughtExceptionHandler"} - {:name "setName"} - {:name "setPriority"} - {:name "setUncaughtExceptionHandler"} - {:name "sleep"} - {:name "start"} - {:name "toString"} - {:name "yield"}]} - java.net.URL - {:allPublicConstructors true - :allPublicFields true - ;; generated with `public-declared-method-names`, see in - ;; `comment` below - :methods [{:name "equals"} - {:name "getAuthority"} - {:name "getContent"} - {:name "getDefaultPort"} - {:name "getFile"} - {:name "getHost"} - {:name "getPath"} - {:name "getPort"} - {:name "getProtocol"} - {:name "getQuery"} - {:name "getRef"} - {:name "getUserInfo"} - {:name "hashCode"} - {:name "openConnection"} - {:name "openStream"} - {:name "sameFile"} - ;; not supported: {:name "setURLStreamHandlerFactory"} - {:name "toExternalForm"} - {:name "toString"} - {:name "toURI"}]} - com.sun.xml.internal.stream.XMLInputFactoryImpl - {:methods [{:name "" :parameterTypes []}]} - com.sun.xml.internal.stream.XMLOutputFactoryImpl - {:methods [{:name "" :parameterTypes []}]}}}) + :custom ~custom-map}) (defmacro gen-class-map [] (let [classes (concat (:all classes) diff --git a/src/babashka/impl/features.clj b/src/babashka/impl/features.clj new file mode 100644 index 00000000..05a3e497 --- /dev/null +++ b/src/babashka/impl/features.clj @@ -0,0 +1,4 @@ +(ns babashka.impl.features + {:no-doc true}) + +(def hsqldb? (= "true" (System/getenv "BABASHKA_FEATURE_HSQLDB"))) diff --git a/src/babashka/impl/jdbc.clj b/src/babashka/impl/jdbc.clj index c18efd83..6891d3b3 100644 --- a/src/babashka/impl/jdbc.clj +++ b/src/babashka/impl/jdbc.clj @@ -1,6 +1,7 @@ (ns babashka.impl.jdbc {:no-doc true} (:require [next.jdbc :as njdbc] + [next.jdbc.sql :as sql] [sci.impl.namespaces :refer [copy-var]] [sci.impl.vars :as vars])) @@ -29,3 +30,8 @@ 'transact (copy-var njdbc/transact next-ns) 'with-transaction (with-meta with-transaction {:sci/macro true})}) + +(def sns (vars/->SciNamespace 'next.jdbc.sql nil)) + +(def next-sql-namespace + {'insert-multi! (copy-var sql/insert-multi! sns)}) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 38e08353..c26d47cf 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -295,7 +295,8 @@ Everything after that is bound to *command-line-args*.")) 'babashka.curl curl-namespace 'cognitect.transit transit-namespace 'bencode.core bencode-namespace - 'next.jdbc jdbc/njdbc-namespace}) + 'next.jdbc jdbc/njdbc-namespace + 'next.jdbc.sql jdbc/next-sql-namespace}) (def bindings {'java.lang.System/exit exit ;; override exit, so we have more control