In this detailed tutorial, we will explore how to build a Clojure library from the ground up and subsequently publish it to Clojars, utilizing the innovative build tool Slim that I recently introduced. Slim is designed to simplify the process of managing Clojure projects, minimizing the complexity typically associated with such tasks.

Publishing an Existing Library

If you already have a library that you'd like to share with the community by publishing it to Clojars, Slim facilitates this process seamlessly with minimal setup required. Assuming you have a deps.edn file already present in your project root, the first step is to incorporate Slim into your dependencies. You can accomplish this by modifying your deps.edn file to include the necessary configurations:

{:aliases {:build {:deps {io.github.abogoyavlensky/slim {:mvn/version "0.3.2"} slipset/deps-deploy {:mvn/version "0.2.2"}} :ns-default slim.lib :exec-args {:version "0.1.0" :lib io.github.githubusername/libraryname :url "https:

Be sure to replace the placeholders in the :exec-args map with your librarys specific details. This simple adjustment allows you to set the environment variables CLOJARS_USERNAME and CLOJARS_PASSWORD accordingly, and then execute the command clojure -T:build deploy to publish your library to Clojars.

For those who prefer to test their library before a full release, you can initially publish a snapshot version by running the command clojure -T:build deploy :snapshot true. More options are available, and I encourage you to consult the documentation for a deeper understanding.

Publishing a New Library

For developers who are eager to create a brand new library based on fresh ideas, I've developed a template called clojure-lib-template that streamlines the process of setting up a new Clojure library. This template comes with numerous benefits, including:

  • A minimalistic design that is easy to grasp
  • Integrated GitHub Actions workflows for continuous integration and deployment, which include publishing to Clojars
  • A comprehensive setup for development tools, including linting, code formatting, dependency management, and testing
  • Preconfigured build and deployment processes utilizing Slim
  • Default licensing under the MIT License

Example Library Idea

To illustrate the process clearly, we will create a simple library that includes a function designed to find an available port on your local machine, aptly named freeport.

Creating a New Project

The first step in developing this project is to establish its structure. If you haven't already installed deps-new, you can do so using the following command:

clojure -Ttools install-latest :lib io.github.seancorfield/deps-new :as new

Next, create a new project with:

clojure -Sdeps '{:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}}' -Tnew create :template io.github.abogoyavlensky/clojure-lib-template :name io.github.yourusername/freeport

Remember to replace yourusername with your actual GitHub username. If you already have Clojure version 1.12.x installed, you can skip the first argument and run the command directly.

Executing this command will generate a new project folder named freeport containing the following structure:

 .clj-kondo/             # Clojure linting configuration .github/                # GitHub Actions workflows and configurations dev/                    # Development configuration directory    user.clj           # User-specific development configuration src/                    # Source code directory    freeport           # Main namespace directory        core.clj      # Main namespace file test/                   # Test files directory    freeport           # Test namespace directory        core_test.clj  # Test namespace file .cljfmt.edn            # Formatting configuration .gitignore              # Git ignore rules .mise.toml             # mise-en-place configuration with system tools versions bb.edn                 # Babashka tasks configuration deps.edn               # Clojure dependencies and aliases LICENSE                # License file CHANGELOG.md           # Changelog file README.md              # Project documentation

Subsequently, youll want to install the required system dependencies. You can do this using the mise tool:

mise trust && mise install

Alternatively, if you prefer a more manual approach, you can consult the .mise.toml file to determine the necessary versions and install them accordingly.

Next, initialize a Git repository and make your first commit:

git initgit add .git commit -am 'Initial commit'

To ensure everything is functioning correctly, run checks for linting, formatting, outdated dependencies, and tests using the following command:

bb check

Should there be any changes resulting from formatting or dependency checks, be sure to commit those updates.

Adding Implementation

Lets now implement the core functionality for our library. Open the src/freeport/core.clj file and insert the following code:

(ns freeport.core  (:import (java.net ServerSocket)))(defn get-freeport []  (with-open [socket (ServerSocket. 0)]    (.getLocalPort socket)))

Next, youll want to write tests to verify our implementation. Open the test/freeport/core_test.clj file and add the following:

(ns freeport.core-test  (:require [clojure.test :refer :all]            [freeport.core :as core])  (:import [java.net ServerSocket]))(defn port-free? [port]  (try    (with-open [_socket (ServerSocket. port)] true)    (catch Exception _ false)))(deftest test-port-is-free-ok  (let [port (core/get-freeport)]    (is (true? (port-free? port)))    (is (<= 1024 port 65535))))

This test checks that the port returned is indeed available and falls within the commonly accepted range of 1024-65535. Run the tests and other checks again to confirm that everything is functioning as expected:

bb check

Once all checks pass, commit your changes:

git commit -am 'Add freeport implementation'

Publishing to Clojars

Before publishing your library, ensure to update the library details within your deps.edn file. Specifically, you'll need to amend the :description and :developer fields:

{;... :aliases {:build {:deps {io.github.abogoyavlensky/slim {:mvn/version "0.3.2"} slipset/deps-deploy {:mvn/version "0.2.2"}} :ns-default slim.lib :exec-args {:version "0.1.0" :lib io.github.yourusername/freeport :url "https:

Next, you must configure your Clojars credentials by setting them as environment variables:

export CLOJARS_USERNAME=yourusernameexport CLOJARS_PASSWORD=yourpassword

With that done, you can publish a snapshot version to Clojars for initial testing:

bb deploy-snapshot

After testing the snapshot in your project and ensuring everything operates correctly, you can proceed to publish a release version:

bb deploy-release

Publishing from GitHub Actions

While deploying from your local system is effective, automating the process through continuous integration and continuous deployment (CI/CD) significantly enhances efficiency. The template weve been using is already configured with GitHub Actions workflows tailored for this purpose.

To finalize this setup, simply establish the environment variables CLOJARS_USERNAME and CLOJARS_PASSWORD in your GitHub repository settings. The workflow is structured to:

  • Publish a snapshot version upon every push to the main branch
  • Publish a release version upon every tag push

Each time you push a new commit to the main branch, the workflow will automatically execute, publishing a snapshot version to Clojars within approximately a minute.

When your library reaches a state where its ready for a new version release, update the version number in your deps.edn file:

{;... :aliases {:build {;... :exec-args {:version "0.1.1" ;... }}}}

After committing these changes to the main branch, create a new Git tag. You can accomplish this manually or utilize the provided command:

bb release

This command serves as a shortcut for both git tag and git push. It generates a new tag corresponding to the latest version defined in your deps.edn file and pushes it to the remote repository, thereby triggering the release workflow.

Summary

In this article, weve navigated through the complete process of building and publishing Clojure libraries. We explored how to use Slim with existing libraries and demonstrated the creation of new libraries from scratch using the clojure-lib-template. I trust that you found this guide beneficial and that it aids you in streamlining your workflow for future Clojure library projects!