Full Clojure project in 5 seconds!

Every time you start a new Clojure project, there is a lot of boilerplate that needs to be added:

  • deps.edn with aliases to run tests and build actions
  • build.clj with build actions
  • .gitignore
  • README.md
  • you name it…

Creating all these can be easily automated with deps-new.

Install deps-new

deps-new intended to be installed as a “tool” for Clojure CLI.

“Tools” in Clojure CLI

Tools are 3rd party libraries that can be installed and than invoked from Clojure CLI using clj -T option. They use their own classpath and do not interfere with the project’s deps.edn if you run tool commands from the project directory.

Few commands you may need:

clj -Ttools list - to view already installed tools

clj -Ttools remove :tool tool-name - to remove the tool

clj -Ttools install tool-path tool-version :as tool-name - to install a new tool

More info about tools you can find in this video.

Installing deps-new tool

Here is a command to add deps-new to your system

1
clojure -Ttools install io.github.seancorfield/deps-new '{:git/tag "v0.4.13"}' :as new

:as new part of the command means that “new” is now a name for deps-new tool on your system. You can use any name you want.

Let’s check our list of tools now:

1
2
3
4
$ clj -Ttools list
TOOL   LIB                              TYPE  VERSION
new    io.github.seancorfield/deps-new  :git  v0.4.13
tools  io.github.clojure/tools.tools    :git  v0.2.8

To check what functions new tool exposes you can run the command:

clojure -A:deps -Tnew help/doc

Create a library

Now, when deps-new is installed, we can use it to create a new library.

The following command will create a library with root namespace vkjr and one source file mycoollib.clj in it

1
2
$ clojure -Tnew lib :name vkjr/mycoollib
  Creating project from org.corfield.new/lib in mycoollib

And here is a content of new library:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
mycoollib
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.clj
├── deps.edn
├── doc
│   └── intro.md
├── pom.xml
├── resources
│   └── .keep
├── src
│   └── vkjr
│       └── mycoollib.clj
└── test
    └── vkjr
        └── mycoollib_test.clj

Top level directory of the library was also named mycoollib but you can override this behavior by providing additional key/value pair to the library creation command :target-dir some-folder-name.

Content of created library

As you can see, there are plenty of automatically generated files in our new lib.

deps.edn from the start contains aliases for testing and build actions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.11.1"}}
 :aliases
 {:test
  {:extra-paths ["test"]
   :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}
                io.github.cognitect-labs/test-runner
                {:git/tag "v0.5.1" :git/sha "dfb30dd"}}}
  :build {:deps {io.github.seancorfield/build-clj
                 {:git/tag "v0.8.2" :git/sha "0ffdb4c"}}
          :ns-default build}}}

src/vkjr/mycoollib.clj contains a dummy function

1
2
3
4
5
6
(ns vkjr.mycoollib)

(defn foo
  "I don't do a whole lot."
  [x]
  (prn x "Hello, World!"))

And in test/vkjr/mycoollib_test.clj already exists one failing test

1
2
3
4
5
6
7
(ns vkjr.mycoollib-test
  (:require [clojure.test :refer :all]
            [vkjr.mycoollib :refer :all]))

(deftest a-test
  (testing "FIXME, I fail."
    (is (= 0 1))))

build.clj contains build actions test, ci, install and deploy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(ns build
  (:refer-clojure :exclude [test])
  (:require [clojure.tools.build.api :as b] ; for b/git-count-revs
            [org.corfield.build :as bb]))

(def lib 'net.clojars.vkjr/mycoollib)
(def version "0.1.0-SNAPSHOT")
#_ ; alternatively, use MAJOR.MINOR.COMMITS:
(def version (format "1.0.%s" (b/git-count-revs nil)))

(defn test "Run the tests." [opts]
  (bb/run-tests opts))

(defn ci "Run the CI pipeline of tests (and build the JAR)." [opts]
  (-> opts
      (assoc :lib lib :version version)
      (bb/run-tests)
      (bb/clean)
      (bb/jar)))

(defn install "Install the JAR locally." [opts]
  (-> opts
      (assoc :lib lib :version version)
      (bb/install)))

(defn deploy "Deploy the JAR to Clojars." [opts]
  (-> opts
      (assoc :lib lib :version version)
      (bb/deploy)))

Note, that build.clj implements actions using the build-clj library instead of standard Clojure’s tools.build. That library hides some unnecessary details and provides the functionality of deploying to Clojars, which is missed in tools.build

There are a bunch of other useful files also: .gitignore, README.md, pom.xml, LICENSE.. real time-saver, ha?

Create an application

Creating an application with deps-new as simple as a library:

1
2
$ clojure -Tnew app :name vkjr/mycoolapp
Creating project from org.corfield.new/app in mycoolapp

And here is a folder structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
mycoolapp
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.clj
├── deps.edn
├── doc
│   └── intro.md
├── pom.xml
├── resources
│   └── .keep
├── src
│   └── vkjr
│       └── mycoolapp.clj
└── test
    └── vkjr
        └── mycoolapp_test.clj

Main differences from the library project:

src/vkjr/mycoolapp.clj contains the (-main) and (greet) functions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(ns vkjr.mycoolapp
  (:gen-class))

(defn greet
  "Callable entry point to the application."
  [data]
  (println (str "Hello, " (or (:name data) "World") "!")))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (greet {:name (first args)}))

deps.edn has additional aliases run-m and run-x to run (main) and (greet) functions respectively

1
2
3
4
5
6
7
...
{:run-m {:main-opts ["-m" "vkjr.mycoolapp"]}
 :run-x {:ns-default vkjr.mycoolapp
         :exec-fn greet
         :exec-args {:name "Clojure"}}
...
}

build.clj creates an uberjar instead of a jar and doesn’t have (install) and (deploy) functions

1
2
3
4
5
6
7
...
(defn ci "Run the CI pipeline of tests (and build the uberjar)." [opts]
  (-> opts
      (assoc :lib lib :version version :main main)
      (bb/run-tests)
      (bb/clean)
      (bb/uber)))

Create a minimal “scratch” project

For the cases when you don’t need a full-blown application, but merely a playground, deps-new supports creating a “scratch” projects:

1
2
$ clojure -Tnew scratch :name playground                                            
Creating project from org.corfield.new/scratch in playground 

Folder structure for such “scratch”:

1
2
3
4
playground
├── deps.edn
└── src
    └── scratch.clj

With almost empty deps.edn and scratch.clj with (-main) and (greet) functions, like in the application example

Using deps-new you can create your own project template, but this is another story… ;)

Clojure Clojure library deps.new