Repository: railwayapp/nixpacks
Pull request #342 (2022-07-21 11:05:39)

Add Clojure provider
Adds Clojure support to Nixpacks, work still in progress but feedback is more than welcomed!


Milo123459: Can you rebase this? I don't think we need all the Yarn cache changes.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 344c57f..31ebb60 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -80,34 +80,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "clap"
-version = "3.2.8"
+version = "3.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
+checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b"
 dependencies = [
- "atty",
  "bitflags",
- "clap_derive",
  "clap_lex",
  "indexmap",
- "once_cell",
- "strsim",
- "termcolor",
  "textwrap",
 ]
 
-[[package]]
-name = "clap_derive"
-version = "3.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
-dependencies = [
- "heck",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "clap_lex"
 version = "0.2.4"
@@ -137,17 +119,15 @@ dependencies = [
  "encode_unicode",
  "libc",
  "once_cell",
- "regex",
  "terminal_size",
- "unicode-width",
  "winapi",
 ]
 
 [[package]]
 name = "darling"
-version = "0.13.4"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
 dependencies = [
  "darling_core",
  "darling_macro",
@@ -155,9 +135,9 @@ dependencies = [
 
 [[package]]
 name = "darling_core"
-version = "0.13.4"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
 dependencies = [
  "fnv",
  "ident_case",
@@ -169,9 +149,9 @@ dependencies = [
 
 [[package]]
 name = "darling_macro"
-version = "0.13.4"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
 dependencies = [
  "darling_core",
  "quote",
@@ -190,12 +170,6 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
-[[package]]
-name = "fs_extra"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
-
 [[package]]
 name = "fuchsia-cprng"
 version = "0.1.1"
@@ -222,21 +196,14 @@ dependencies = [
  "aho-corasick",
  "bstr",
  "fnv",
- "log",
  "regex",
 ]
 
 [[package]]
 name = "hashbrown"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
-
-[[package]]
-name = "heck"
-version = "0.4.0"
+version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 
 [[package]]
 name = "hermit-abi"
@@ -293,15 +260,6 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
 
-[[package]]
-name = "log"
-version = "0.4.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
-
 [[package]]
 name = "memchr"
 version = "2.5.0"
@@ -319,7 +277,6 @@ dependencies = [
  "clap",
  "colored",
  "console",
- "fs_extra",
  "globset",
  "indoc",
  "regex",
@@ -329,7 +286,6 @@ dependencies = [
  "serde_yaml",
  "tempdir",
  "textwrap",
- "thiserror",
  "toml",
  "uuid",
  "wait-timeout",
@@ -344,33 +300,9 @@ checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
 
 [[package]]
 name = "os_str_bytes"
-version = "6.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
-
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+version = "6.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
-]
+checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
 
 [[package]]
 name = "proc-macro2"
@@ -470,18 +402,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.138"
+version = "1.0.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
+checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.138"
+version = "1.0.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
+checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -501,9 +433,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "1.14.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
+checksum = "89df7a26519371a3cce44fbb914c2819c84d9b897890987fa3ab096491cc0ea8"
 dependencies = [
  "serde",
  "serde_with_macros",
@@ -511,9 +443,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "1.5.2"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
+checksum = "de337f322382fcdfbb21a014f7c224ee041a23785651db67b9827403178f698f"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -523,9 +455,9 @@ dependencies = [
 
 [[package]]
 name = "serde_yaml"
-version = "0.8.24"
+version = "0.8.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
+checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
 dependencies = [
  "indexmap",
  "ryu",
@@ -533,12 +465,6 @@ dependencies = [
  "yaml-rust",
 ]
 
-[[package]]
-name = "smawk"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
-
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -566,15 +492,6 @@ dependencies = [
  "remove_dir_all",
 ]
 
-[[package]]
-name = "termcolor"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
-dependencies = [
- "winapi-util",
-]
-
 [[package]]
 name = "terminal_size"
 version = "0.1.17"
@@ -590,31 +507,6 @@ name = "textwrap"
 version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
-dependencies = [
- "smawk",
- "unicode-linebreak",
- "unicode-width",
-]
-
-[[package]]
-name = "thiserror"
-version = "1.0.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
 
 [[package]]
 name = "toml"
@@ -627,24 +519,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
-
-[[package]]
-name = "unicode-linebreak"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
-dependencies = [
- "regex",
-]
-
-[[package]]
-name = "unicode-width"
-version = "0.1.9"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
 
 [[package]]
 name = "uuid"
@@ -655,12 +532,6 @@ dependencies = [
  "getrandom",
 ]
 
-[[package]]
-name = "version_check"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
-
 [[package]]
 name = "wait-timeout"
 version = "0.2.0"
diff --git a/Cargo.toml b/Cargo.toml
index dab9416..aff2f1b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,24 +21,22 @@ path = "src/lib.rs"
 
 [dependencies]
 anyhow = "1.0.58"
-clap = { version = "3.2.8", features = ["derive"] }
+clap = { version = "3.2.14", features = ["std"], default-features = false }
 colored = "2.0.0"
-fs_extra = "1.2.0"
-globset = "0.4.9"
+globset = { default-features = false, version = "0.4.9" }
 walkdir = "2.3.2"
 indoc = "1.0.6"
 regex = "1.6.0"
-serde = { version = "1.0.138", features = ["derive"] }
+serde = { version = "1.0.140", default-features = false }
 serde_json = "1.0.82"
-serde_yaml = "0.8.24"
-serde_with = "1.14.0"
+serde_yaml = "0.8.26"
+serde_with = { features = ["macros"], default-features = false, version = "2.0.0" }
 tempdir = "0.3.7"
-thiserror = "1.0.31"
 toml = "0.5.9"
-uuid = { version = "1.1.2", features = ["v4"] }
+uuid = { version = "1.1.2", features = ["v4"], default-features = false }
 wait-timeout = "0.2.0"
 base64 = "0.13.0"
-console = "0.15.0"
+console = { default-features = false, version = "0.15.0" }
 box_drawing = "0.1.2"
-textwrap = "0.15.0"
+textwrap = { default-features = false, version = "0.15.0" }
 cargo_toml = "0.11.5"
diff --git a/docs/pages/docs/providers/clojure.md b/docs/pages/docs/providers/clojure.md
new file mode 100644
index 0000000..27414d7
--- /dev/null
+++ b/docs/pages/docs/providers/clojure.md
@@ -0,0 +1,33 @@
+---
+title: Clojure
+---
+
+# {% $markdoc.frontmatter.title %}
+
+Clojure is detected if a `project.clj` file is found.
+
+## Setup
+
+The following JDK versions are available
+
+- `8`  (Default)
+- `11`
+- `latest`
+
+The version can be overriden by
+
+- Setting the `NIXPACKS_JDK_VERSION` environment variable
+- Setting the version in a `.jdk-version` file
+
+## Build
+
+```
+lein uberjar
+```
+
+
+## Start
+
+```
+java $JAVA_OPTS -jar target/uberjar/*standalone.jar
+```
\ No newline at end of file
diff --git a/examples/clojure-jdk11/.jdk-version b/examples/clojure-jdk11/.jdk-version
new file mode 100644
index 0000000..9d60796
--- /dev/null
+++ b/examples/clojure-jdk11/.jdk-version
@@ -0,0 +1,1 @@
+11
\ No newline at end of file
diff --git a/examples/clojure-jdk11/project.clj b/examples/clojure-jdk11/project.clj
new file mode 100644
index 0000000..29c18ad
--- /dev/null
+++ b/examples/clojure-jdk11/project.clj
@@ -0,0 +1,10 @@
+(defproject clojure-example "0.1.0-SNAPSHOT"
+  :description "FIXME: write description"
+  :url "http://example.com/FIXME"
+  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
+            :url "https://www.eclipse.org/legal/epl-2.0/"}
+  :dependencies [[org.clojure/clojure "1.10.3"]]
+  :main ^:skip-aot clojure-example.core
+  :target-path "target/%s"
+  :profiles {:uberjar {:aot :all
+                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
diff --git a/examples/clojure-jdk11/src/clojure_example/core.clj b/examples/clojure-jdk11/src/clojure_example/core.clj
new file mode 100644
index 0000000..2931157
--- /dev/null
+++ b/examples/clojure-jdk11/src/clojure_example/core.clj
@@ -0,0 +1,7 @@
+(ns clojure-example.core
+  (:gen-class))
+
+(defn -main
+  "I don't do a whole lot ... yet."
+  [& args]
+  (println "Hello, World From Clojure!"))
diff --git a/examples/clojure/project.clj b/examples/clojure/project.clj
new file mode 100644
index 0000000..29c18ad
--- /dev/null
+++ b/examples/clojure/project.clj
@@ -0,0 +1,10 @@
+(defproject clojure-example "0.1.0-SNAPSHOT"
+  :description "FIXME: write description"
+  :url "http://example.com/FIXME"
+  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
+            :url "https://www.eclipse.org/legal/epl-2.0/"}
+  :dependencies [[org.clojure/clojure "1.10.3"]]
+  :main ^:skip-aot clojure-example.core
+  :target-path "target/%s"
+  :profiles {:uberjar {:aot :all
+                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
diff --git a/examples/clojure/src/clojure_example/core.clj b/examples/clojure/src/clojure_example/core.clj
new file mode 100644
index 0000000..2931157
--- /dev/null
+++ b/examples/clojure/src/clojure_example/core.clj
@@ -0,0 +1,7 @@
+(ns clojure-example.core
+  (:gen-class))
+
+(defn -main
+  "I don't do a whole lot ... yet."
+  [& args]
+  (println "Hello, World From Clojure!"))
diff --git a/src/lib.rs b/src/lib.rs
index 1e45b44..3d0b86e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,11 +14,11 @@ use crate::nixpacks::{
 };
 use anyhow::Result;
 use providers::{
-    crystal::CrystalProvider, csharp::CSharpProvider, dart::DartProvider, deno::DenoProvider,
-    fsharp::FSharpProvider, go::GolangProvider, haskell::HaskellStackProvider, java::JavaProvider,
-    node::NodeProvider, php::PhpProvider, python::PythonProvider, ruby::RubyProvider,
-    rust::RustProvider, staticfile::StaticfileProvider, swift::SwiftProvider, zig::ZigProvider,
-    Provider,
+    clojure::ClojureProvider, crystal::CrystalProvider, csharp::CSharpProvider, dart::DartProvider,
+    deno::DenoProvider, fsharp::FSharpProvider, go::GolangProvider, haskell::HaskellStackProvider,
+    java::JavaProvider, node::NodeProvider, php::PhpProvider, python::PythonProvider,
+    ruby::RubyProvider, rust::RustProvider, staticfile::StaticfileProvider, swift::SwiftProvider,
+    zig::ZigProvider, Provider,
 };
 
 mod chain;
@@ -33,6 +33,7 @@ pub fn get_providers() -> &'static [&'static dyn Provider] {
         &DartProvider {},
         &DenoProvider {},
         &FSharpProvider {},
+        &ClojureProvider {},
         &GolangProvider {},
         &HaskellStackProvider {},
         &JavaProvider {},
diff --git a/src/providers/clojure.rs b/src/providers/clojure.rs
new file mode 100644
index 0000000..e4024ca
--- /dev/null
+++ b/src/providers/clojure.rs
@@ -0,0 +1,174 @@
+use super::Provider;
+use crate::nixpacks::{
+    app::App,
+    environment::Environment,
+    nix::pkg::Pkg,
+    phase::{BuildPhase, SetupPhase, StartPhase},
+};
+use anyhow::Result;
+use regex::Regex;
+
+const DEFAULT_JDK_PKG_NAME: &'static &str = &"jdk8";
+pub struct ClojureProvider {}
+
+impl Provider for ClojureProvider {
+    fn name(&self) -> &str {
+        "clojure"
+    }
+
+    fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
+        Ok(app.includes_file("project.clj"))
+    }
+
+    fn setup(&self, _app: &App, _env: &Environment) -> Result<Option<SetupPhase>> {
+        Ok(Some(SetupPhase::new(vec![
+            Pkg::new("leiningen"),
+            Pkg::new("jdk8"),
+        ])))
+    }
+
+    fn build(&self, _app: &App, _env: &Environment) -> Result<Option<BuildPhase>> {
+        Ok(Some(BuildPhase::new("lein uberjar".to_string())))
+    }
+
+    fn start(&self, _app: &App, _env: &Environment) -> Result<Option<StartPhase>> {
+        Ok(Some(StartPhase::new(
+            "java $JAVA_OPTS -jar target/uberjar/*standalone.jar".to_string(),
+        )))
+    }
+}
+
+impl ClojureProvider {
+    fn get_custom_version(app: &App, env: &Environment) -> Result<String> {
+        // Fetch version from configs
+        let mut custom_version = env.get_config_variable("JDK_VERSION");
+
+        // If not from configs, get it from the .jdk-version file
+        if custom_version.is_none() && app.includes_file(".jdk-version") {
+            custom_version = Some(app.read_file(".jdk-version")?);
+        }
+
+        match custom_version {
+            Some(v) => Ok(v),
+            None => Ok(DEFAULT_JDK_PKG_NAME.to_string()),
+        }
+    }
+
+    fn parse_custom_version(custom_version: String) -> Result<String> {
+        // Regex for reading JDK versions (e.g. 8 or 11 or latest)
+        let jdk_regex = Regex::new(r"(^[0-9][0-9]?$)|(^latest$)")?;
+
+        // Capture matches
+        let matches = jdk_regex.captures(custom_version.as_str().trim());
+
+        // If no matches, just use default
+        if matches.is_none() {
+            return Ok(DEFAULT_JDK_PKG_NAME.to_string());
+        }
+
+        let matches = matches.unwrap();
+        let matched_value = if matches.get(0).is_some() {
+            matches.get(0)
+        } else {
+            matches.get(1)
+        };
+
+        let value = match matched_value {
+            Some(m) => m.as_str(),
+            None => "_",
+        };
+
+        Ok(value.to_string())
+    }
+
+    pub fn get_nix_jdk_package(app: &App, env: &Environment) -> Result<Pkg> {
+        let custom_version = ClojureProvider::get_custom_version(app, env)?;
+        let parsed_version = ClojureProvider::parse_custom_version(custom_version)?;
+
+        let pkg_name = match parsed_version.as_str() {
+            "latest" => "jdk",
+            "11" => "jdk11",
+            _ => DEFAULT_JDK_PKG_NAME, // 8 or any other value
+        };
+
+        Ok(Pkg::new(pkg_name))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::nixpacks::{app::App, environment::Environment, nix::pkg::Pkg};
+    use std::collections::HashMap;
+
+    #[test]
+    fn test_no_version() -> Result<()> {
+        assert_eq!(
+            ClojureProvider::get_nix_jdk_package(
+                &App::new("./examples/clojure")?,
+                &Environment::default()
+            )?,
+            Pkg::new(DEFAULT_JDK_PKG_NAME)
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_custom_version() -> Result<()> {
+        assert_eq!(
+            ClojureProvider::get_nix_jdk_package(
+                &App::new("./examples/clojure-jdk11")?,
+                &Environment::default()
+            )?,
+            Pkg::new("jdk11")
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_custom_latest_version() -> Result<()> {
+        assert_eq!(
+            ClojureProvider::get_nix_jdk_package(
+                &App::new("./examples/clojure-jdk-latest")?,
+                &Environment::default()
+            )?,
+            Pkg::new("jdk")
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_latest_version_from_environment_variable() -> Result<()> {
+        assert_eq!(
+            ClojureProvider::get_nix_jdk_package(
+                &App::new("./examples/clojure-jdk-latest")?,
+                &Environment::new(HashMap::from([(
+                    "NIXPACKS_JDK_VERSION".to_string(),
+                    "latest".to_string()
+                )]))
+            )?,
+            Pkg::new("jdk")
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_version_from_environment_variable() -> Result<()> {
+        assert_eq!(
+            ClojureProvider::get_nix_jdk_package(
+                &App::new("./examples/clojure")?,
+                &Environment::new(HashMap::from([(
+                    "NIXPACKS_JDK_VERSION".to_string(),
+                    "11".to_string()
+                )]))
+            )?,
+            Pkg::new("jdk11")
+        );
+
+        Ok(())
+    }
+}
diff --git a/src/providers/mod.rs b/src/providers/mod.rs
index b1f6238..d8f4539 100644
--- a/src/providers/mod.rs
+++ b/src/providers/mod.rs
@@ -5,6 +5,7 @@ use crate::nixpacks::{
 };
 use anyhow::Result;
 
+pub mod clojure;
 pub mod crystal;
 pub mod csharp;
 pub mod dart;


Test patch:
diff --git a/examples/clojure-jdk-latest/.jdk-version b/examples/clojure-jdk-latest/.jdk-version
new file mode 100644
index 0000000..b9bc2fd
--- /dev/null
+++ b/examples/clojure-jdk-latest/.jdk-version
@@ -0,0 +1,1 @@
+latest
\ No newline at end of file
diff --git a/examples/clojure-jdk-latest/project.clj b/examples/clojure-jdk-latest/project.clj
new file mode 100644
index 0000000..29c18ad
--- /dev/null
+++ b/examples/clojure-jdk-latest/project.clj
@@ -0,0 +1,10 @@
+(defproject clojure-example "0.1.0-SNAPSHOT"
+  :description "FIXME: write description"
+  :url "http://example.com/FIXME"
+  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
+            :url "https://www.eclipse.org/legal/epl-2.0/"}
+  :dependencies [[org.clojure/clojure "1.10.3"]]
+  :main ^:skip-aot clojure-example.core
+  :target-path "target/%s"
+  :profiles {:uberjar {:aot :all
+                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
diff --git a/examples/clojure-jdk-latest/src/clojure_example/core.clj b/examples/clojure-jdk-latest/src/clojure_example/core.clj
new file mode 100644
index 0000000..2931157
--- /dev/null
+++ b/examples/clojure-jdk-latest/src/clojure_example/core.clj
@@ -0,0 +1,7 @@
+(ns clojure-example.core
+  (:gen-class))
+
+(defn -main
+  "I don't do a whole lot ... yet."
+  [& args]
+  (println "Hello, World From Clojure!"))
diff --git a/tests/docker_run_tests.rs b/tests/docker_run_tests.rs
index 0c27563..a75dee8 100644
--- a/tests/docker_run_tests.rs
+++ b/tests/docker_run_tests.rs
@@ -550,3 +550,10 @@ fn test_ruby_rails() {
 
     assert!(output.contains("Rails 7"));
 }
+
+#[test]
+fn test_clojure() {
+    let name = simple_build("./examples/clojure");
+    let output = run_image(name, None);
+    assert_eq!(output, "Hello, World From Clojure!");
+}
diff --git a/tests/generate_plan_tests.rs b/tests/generate_plan_tests.rs
index dbe59b2..a90dbbe 100644
--- a/tests/generate_plan_tests.rs
+++ b/tests/generate_plan_tests.rs
@@ -939,3 +939,21 @@ fn test_ruby_sinatra() -> Result<()> {
     );
     Ok(())
 }
+
+#[test]
+fn test_clojure() -> Result<()> {
+    let plan = simple_gen_plan("./examples/clojure");
+    assert_eq!(
+        plan.setup.unwrap().pkgs,
+        vec![Pkg::new("leiningen"), Pkg::new("jdk8"),]
+    );
+    assert_eq!(
+        plan.build.unwrap().cmds,
+        Some(vec!["lein uberjar".to_string()])
+    );
+    assert_eq!(
+        plan.start.unwrap().cmd,
+        Some("java $JAVA_OPTS -jar target/uberjar/*standalone.jar".to_string())
+    );
+    Ok(())
+}