├── .github ├── FUNDING.yml └── workflows │ ├── continuous-integration-workflow.yml │ └── continuous-deployment-workflow.yml ├── .gitignore ├── CHANGELOG.md ├── test └── shadow_git_inject │ └── core_test.clj ├── project.clj ├── src └── shadow_git_inject │ └── core.clj ├── README.md └── LICENSE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mike-thompson-day8 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /*.iml 3 | /target 4 | /classes 5 | /checkouts 6 | profiles.clj 7 | pom.xml 8 | pom.xml.asc 9 | *.jar 10 | *.class 11 | /.lein-* 12 | /.nrepl-port 13 | .hgignore 14 | .hg/ 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | #### Fixed 4 | 5 | - Use strings instead of regex literals for patterns in config. Fixes [#2](https://github.com/day8/shadow-git-inject/issues/2). 6 | Thanks to [@schpaa](https://github.com/schpaa) for reporting the issue. 7 | 8 | ## [0.0.4] - 2021-04-01 9 | 10 | #### Fixed 11 | 12 | - Remove unnecessary update-ins. No user visible changes. See [#1](https://github.com/day8/shadow-git-inject/issues/1). 13 | 14 | ## [0.0.3] - 2021-04-01 15 | 16 | #### Fixed 17 | 18 | - Remove debugging output leftover from last change. 19 | 20 | ## [0.0.2] - 2021-04-01 21 | 22 | #### Changed 23 | 24 | - Only walk `:closure-defines` in `build-state` (i.e. the config). This fixes 25 | [#1](https://github.com/day8/shadow-git-inject/issues/1). Thanks to [@thheller](https://github.com/thheller) for reporting and proposing fix. 26 | 27 | ## [0.0.1] - 2021-03-31 28 | 29 | Initial release. 30 | -------------------------------------------------------------------------------- /test/shadow_git_inject/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns shadow-git-inject.core-test 2 | (:require 3 | [clojure.test :refer [deftest is]] 4 | [shadow-git-inject.core :refer [hook]])) 5 | 6 | (deftest hook-test 7 | (let [build-state {:target :browser 8 | :compiler-options {:closure-defines {'app/version :shadow-git-inject/version 9 | 'app/timestamp :shadow-git-inject/build-iso-date-time 10 | 'app/username :shadow-git-inject/username}}} 11 | build-state' (hook build-state)] 12 | (is (string? (get-in build-state' [:compiler-options :closure-defines 'app/version]))) 13 | (is (string? (get-in build-state' [:compiler-options :closure-defines 'app/timestamp]))) 14 | (is (string? (get-in build-state' [:compiler-options :closure-defines 'app/username]))))) 15 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject day8/shadow-git-inject "lein-git-inject/version" 2 | :description "Injects shadow-cljs.edn with some execution context." 3 | :url "https://github.com/day8/shadow-git-inject" 4 | :licence {:name "EPL-2.0" 5 | :url "https://www.eclipse.org/legal/epl-2.0/"} 6 | 7 | :dependencies [[org.clojure/clojure "1.10.3" :scope "provided"]] 8 | 9 | ;; This is only for versioning of the shadow-git-inject artefact on 10 | ;; Clojars, it is not a dependency of shadow-git-inject. 11 | :plugins [[day8/lein-git-inject "0.0.14"]] 12 | 13 | :middleware [leiningen.git-inject/middleware] 14 | 15 | :release-tasks [["vcs" "assert-committed"] 16 | ["deploy" "clojars"]] 17 | 18 | :deploy-repositories [["clojars" {:sign-releases false 19 | :url "https://clojars.org/repo" 20 | :username :env/CLOJARS_USERNAME 21 | :password :env/CLOJARS_TOKEN}]]) 22 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration-workflow.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push] 3 | 4 | jobs: 5 | test: 6 | name: Test 7 | runs-on: ubuntu-20.04 8 | container: 9 | # Source: https://github.com/day8/dockerfile-for-dev-ci-image 10 | image: ghcr.io/day8/dockerfile-for-dev-ci-image/core:2 11 | credentials: 12 | username: ${{ github.actor }} 13 | password: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | # All of the Git history is required for day8/lein-git-inject to determine the version string. 18 | fetch-depth: 0 19 | - name: Maven cache 20 | uses: actions/cache@v2 21 | with: 22 | path: /root/.m2/repository 23 | key: ${{ runner.os }}-maven-${{ hashFiles('project.clj', '.github/workflows/**') }} 24 | restore-keys: | 25 | ${{ runner.os }}-maven- 26 | - run: lein test 27 | if: steps.maven-cache.outputs.cache-hit != 'true' 28 | - run: lein -o test 29 | if: steps.maven-cache.outputs.cache-hit == 'true' 30 | - name: Slack notification 31 | uses: homoluctus/slatify@v2.0.1 32 | if: failure() || cancelled() 33 | with: 34 | type: ${{ job.status }} 35 | job_name: lein-git-inject Tests 36 | channel: '#oss-robots' 37 | url: ${{ secrets.SLACK_WEBHOOK_URL }} 38 | commit: true 39 | token: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment-workflow.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+*" 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-20.04 11 | container: 12 | # Source: https://github.com/day8/dockerfile-for-dev-ci-image 13 | image: ghcr.io/day8/dockerfile-for-dev-ci-image/core:2 14 | credentials: 15 | username: ${{ github.actor }} 16 | password: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | # All of the Git history is required for day8/lein-git-inject to determine the version string. 21 | fetch-depth: 0 22 | - name: Maven cache 23 | uses: actions/cache@v2 24 | with: 25 | path: /root/.m2/repository 26 | key: ${{ runner.os }}-maven-${{ hashFiles('project.clj', '.github/workflows/**') }} 27 | restore-keys: | 28 | ${{ runner.os }}-maven- 29 | - run: lein test 30 | if: steps.maven-cache.outputs.cache-hit != 'true' 31 | - run: lein -o test 32 | if: steps.maven-cache.outputs.cache-hit == 'true' 33 | - name: Slack notification 34 | uses: homoluctus/slatify@v2.0.1 35 | if: failure() || cancelled() 36 | with: 37 | type: ${{ job.status }} 38 | job_name: lein-git-inject Tests 39 | channel: '#oss-robots' 40 | url: ${{ secrets.SLACK_WEBHOOK_URL }} 41 | commit: true 42 | token: ${{ secrets.GITHUB_TOKEN }} 43 | release: 44 | name: Release 45 | needs: test 46 | runs-on: ubuntu-20.04 47 | container: 48 | # Source: https://github.com/day8/dockerfile-for-dev-ci-image 49 | image: ghcr.io/day8/dockerfile-for-dev-ci-image/core:2 50 | credentials: 51 | username: ${{ github.actor }} 52 | password: ${{ secrets.GITHUB_TOKEN }} 53 | steps: 54 | - uses: actions/checkout@v2 55 | with: 56 | # All of the Git history is required for day8/lein-git-inject to determine the version string. 57 | fetch-depth: 0 58 | - name: Maven cache 59 | uses: actions/cache@v2 60 | with: 61 | path: /root/.m2/repository 62 | key: ${{ runner.os }}-maven-${{ hashFiles('project.clj', '.github/workflows/**') }} 63 | restore-keys: | 64 | ${{ runner.os }}-maven- 65 | - name: Run lein release 66 | if: steps.maven-cache.outputs.cache-hit != 'true' 67 | env: 68 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 69 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_TOKEN }} 70 | GITHUB_USERNAME: ${{ github.actor }} 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | run: | 73 | lein release 74 | - name: Run lein -o release 75 | if: steps.maven-cache.outputs.cache-hit == 'true' 76 | env: 77 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 78 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_TOKEN }} 79 | GITHUB_USERNAME: ${{ github.actor }} 80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | run: | 82 | lein -o release 83 | - name: Slack notification 84 | uses: homoluctus/slatify@v2.0.1 85 | if: always() 86 | with: 87 | type: ${{ job.status }} 88 | job_name: lein-git-inject Release 89 | channel: '#oss-robots' 90 | url: ${{ secrets.SLACK_WEBHOOK_URL }} 91 | commit: true 92 | token: ${{ secrets.GITHUB_TOKEN }} 93 | -------------------------------------------------------------------------------- /src/shadow_git_inject/core.clj: -------------------------------------------------------------------------------- 1 | (ns shadow-git-inject.core 2 | (:require 3 | [clojure.walk :as walk] 4 | [clojure.string :as string] 5 | [clojure.java.shell :refer [sh]] 6 | [clojure.java.io :as io]) 7 | (:import 8 | (java.io BufferedReader StringReader IOException) 9 | (java.time LocalDateTime) 10 | (java.time.format DateTimeFormatter)) 11 | (:refer-clojure :exclude [boolean?])) 12 | 13 | (def default-config 14 | "The default configuration values." 15 | {:git "git" 16 | :describe-pattern "(?.*)-(?\\d+)-g(?[0-9a-f]*)(?(-dirty)?)" 17 | :version-pattern "^v(\\d+\\.\\d+\\.\\d+)$"}) 18 | 19 | (defmacro let-groups 20 | "Let for binding groups out of a j.u.r.Pattern j.u.r.Matcher." 21 | {:style/indent [1]} 22 | [[bindings m] & body] 23 | (let [s (with-meta (gensym "matcher") {:tag java.util.regex.Matcher})] 24 | `(let [~s ~m 25 | ~@(mapcat identity 26 | (for [b bindings] 27 | `[~b (.group ~s ~(name b))]))] 28 | ~@body))) 29 | 30 | ;; Duplicate of clojure.core/boolean? as it is only available in Clojure 1.9+ 31 | (defn boolean? 32 | "Return true if x is a Boolean" 33 | [x] (instance? Boolean x)) 34 | 35 | (defn ensure-pattern 36 | "Given a string, compiles it to a java.util.regex.Pattern." 37 | [x label] 38 | (cond (string? x) 39 | (re-pattern x) 40 | 41 | (instance? java.util.regex.Pattern x) 42 | x 43 | 44 | :else 45 | (throw (IllegalArgumentException. (str "shadow-git-inject " label " requires a string or a java.util.regex.Pattern!"))))) 46 | 47 | (defn initial-commit 48 | [{:keys [git] :as config}] 49 | (let [{:keys [exit out] :as child} (apply sh [git "rev-list" "--max-parents=0" "HEAD"])] 50 | (if-not (= exit 0) 51 | (binding [*out* *err*] 52 | (printf "Warning: shadow-git-inject git exited %d\n%s\n\n" 53 | exit child) 54 | (.flush *out*) 55 | nil) 56 | (first (string/split-lines (string/trim out)))))) 57 | 58 | (defn parse-tags 59 | [config out] 60 | (reduce 61 | (fn [ret line] 62 | (if-let [[_ decorations] (re-find #"[0-9a-fA-F]{7} \(([^)]*)\) .*" line)] 63 | (reduce 64 | (fn [ret decoration] 65 | (if-let [[_ tag] (re-find #"tag: ([0-9a-zA-Z`!@#$%&()-_+={}|;'<>,./]+)" decoration)] 66 | (conj ret tag) 67 | ret)) 68 | ret 69 | (string/split decorations #",")) 70 | ret)) 71 | [] 72 | (string/split-lines (string/trim out)))) 73 | 74 | (defn tags 75 | [{:keys [git] :as config}] 76 | (let [{:keys [exit out] :as child} (apply sh [git "log" "--oneline" "--decorate" "--simplify-by-decoration" "--ancestry-path" (str (initial-commit config) "..HEAD")])] 77 | (if-not (= exit 0) 78 | (binding [*out* *err*] 79 | (printf "Warning: shadow-git-inject git exited %d\n%s\n\n" 80 | exit child) 81 | (.flush *out*) 82 | nil) 83 | (parse-tags config out)))) 84 | 85 | (defn latest-version-tag 86 | [{:keys [version-pattern] :as config}] 87 | (let [pattern (ensure-pattern version-pattern ":version-pattern")] 88 | (first (filter #(re-matches pattern %) (tags config))))) 89 | 90 | (defn resolve-ref 91 | "Fetches the git ref of ref, being a tag or ref name." 92 | [{:keys [git] :as config} ref] 93 | (let [{:keys [exit out] :as child} (apply sh [git "rev-parse" "--verify" ref])] 94 | (if-not (= exit 0) 95 | (binding [*out* *err*] 96 | (printf "Warning: shadow-git-inject git exited %d\n%s\n\n" 97 | exit child) 98 | (.flush *out*) 99 | nil) 100 | (string/trim out)))) 101 | 102 | (defn parse-describe 103 | "Used to parse the output of git-describe, using the configured `describe-pattern`. 104 | Returns a map `{:tag, :ahead, :ahead?, :ref, :ref-short, :dirty?}` 105 | if the pattern matches, otherwise returns the empty map." 106 | [{:keys [describe-pattern] :as config} out] 107 | (let [pattern (ensure-pattern describe-pattern ":describe-pattern") 108 | matcher (re-matcher pattern out)] 109 | (if-not (.matches matcher) 110 | (do (binding [*out* *err*] 111 | (printf (str "Warning: shadow-git-inject couldn't match the current repo status:\n%s\n\n" 112 | "Against pattern:\n%s\n\n") 113 | (pr-str out) pattern) 114 | (.flush *out*)) 115 | {}) 116 | (let-groups [[tag ahead ref dirty] matcher] 117 | {:tag tag 118 | :ahead (Integer/parseInt ahead) 119 | :ahead? (not= ahead "0") 120 | :ref (resolve-ref config "HEAD") 121 | :ref-short ref 122 | :dirty? (not= "" dirty)})))) 123 | 124 | (defn describe 125 | "Uses git-describe to parse the status of the repository. 126 | Using the configured `git` and `describe-pattern` to parse the output. 127 | Returns a map `{:tag, :ahead, :ahead?, :ref, :ref-short, :dirty?}` 128 | if the pattern matches, otherwise returns the empty map." 129 | [{:keys [git] :as config}] 130 | (let [version-tag (latest-version-tag config)] 131 | (if-not version-tag 132 | {} 133 | (let [{:keys [exit out] :as child} (apply sh [git "describe" "--tags" "--dirty" "--long" "--match" version-tag])] 134 | (if-not (= exit 0) 135 | (binding [*out* *err*] 136 | (printf "Warning: shadow-git-inject git exited %d\n%s\n\n" 137 | exit child) 138 | (.flush *out*) 139 | {}) 140 | (parse-describe config (string/trim out))))))) 141 | 142 | (defn git-status-to-version 143 | [{:keys [version-pattern ignore-dirty?] :as config}] 144 | (try 145 | (let [{:keys [tag ahead ahead? dirty? ref-short]} (describe config) 146 | ignore-dirty?' (cond 147 | (and (keyword? ignore-dirty?) 148 | (= (namespace ignore-dirty?) "env")) 149 | (= "true" (System/getenv (string/upper-case (name ignore-dirty?)))) 150 | 151 | (boolean? ignore-dirty?) 152 | ignore-dirty? 153 | 154 | :default 155 | false)] 156 | (if-not (string? tag) 157 | ;; If git status is nil (e.g. IntelliJ evaluating project.clj): 158 | "git-version-tag-not-found" 159 | (let [[_ version] (re-find (re-pattern version-pattern) tag)] 160 | (if (and (not ahead?) 161 | (or ignore-dirty?' (not dirty?))) 162 | ;; If this is a release version: 163 | version 164 | (str version "-" ahead "-" ref-short "-SNAPSHOT"))))) 165 | (catch IOException _ 166 | ;; If git binary is not available (e.g. not in path): 167 | "git-command-not-found"))) 168 | 169 | (def x->f 170 | {:shadow-git-inject/build-iso-date-time (fn [_] (.format (LocalDateTime/now) DateTimeFormatter/ISO_DATE_TIME)) 171 | :shadow-git-inject/build-iso-week-date (fn [_] (.format (LocalDateTime/now) DateTimeFormatter/ISO_WEEK_DATE)) 172 | :shadow-git-inject/version git-status-to-version 173 | :shadow-git-inject/username (fn [_] (System/getProperty "user.name"))}) 174 | 175 | (defn inject 176 | [state config] 177 | (walk/prewalk 178 | (fn [x] 179 | (reduce-kv 180 | (fn [ret k f] 181 | (cond 182 | (keyword? x) 183 | (if (= x k) 184 | (f config) 185 | ret) 186 | 187 | (string? x) 188 | (let [s (str (namespace k) "/" (name k))] 189 | (if (string/includes? x s) 190 | (string/replace x (re-pattern s) (f config)) 191 | ret)) 192 | 193 | :default 194 | ret)) 195 | x 196 | x->f)) 197 | state)) 198 | 199 | (defn hook 200 | {:shadow.build/stage :configure} 201 | [{:keys [git-inject] 202 | :as build-state} & args] 203 | (let [config (merge default-config git-inject)] 204 | (update-in build-state [:compiler-options :closure-defines] inject config))) 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 5 | [![Clojars Project](https://img.shields.io/clojars/v/day8/shadow-git-inject?style=for-the-badge&logo=clojure&logoColor=fff)](https://clojars.org/day8/shadow-git-inject) 6 | [![GitHub issues](https://img.shields.io/github/issues-raw/day8/shadow-git-inject?style=for-the-badge)](https://github.com/day8/shadow-git-inject/issues) 7 | [![License](https://img.shields.io/github/license/day8/shadow-git-inject?style=for-the-badge)](LICENSE) 8 | 9 | # shadow-git-inject 10 | 11 | This is a shadow-cljs "build hook" which processes your build "configuration". 12 | 13 | At build time, it will compute an application's "version" from the "ambient git context" (think latest tag) 14 | so that it can be "injected" into your application. 15 | 16 | This hook assumes that you tag your releases, and that you'd like an automatic way of injecting the most recent tag into your application. 17 | 18 | ## Quick Start 19 | 20 | Your `shadow-cljs` build configuration will look like this: 21 | ```clj 22 | :compiler-options {:closure-defines {my-app.config/version :shadow-git-inject/version}} 23 | ``` 24 | 25 | You'll notice that it contains a placeholder keyword for the current version: `:shadow-git-inject/version`. 26 | 27 | At build time, this build hook will: 28 | 1. apply ***a two-rule method*** to compute the "version" from ***the ambient git context***. We refer to this as `the computed version`. 29 | 2. replace the placeholder keyword with `the computed version` 30 | 31 | Because this version is used within the `closure-define` section, 32 | it allows you to embed `the computed version` into your ClojureScript 33 | application (into the var `my-app.config/version` in the example config above), 34 | making it readily available at run-time for purposes like logging. 35 | 36 | ## The Ambient Git Context 37 | 38 | Imagine you are at the command line in a git repo, and you execute: 39 | ```sh 40 | $ git describe --tags --dirty=-dirty 41 | ``` 42 | If the latest tag in your branch was `v1.0.4`, this command might output something like: 43 | ```sh 44 | v1.0.4-3-g975b-dirty 45 | ``` 46 | which encodes four (hyphen separated) values which we refer to as "the ambient git context": 47 | - the latest git tag: "v1.0.4" 48 | - the number of commits the repo is currently "ahead" of that latest tag: "3" 49 | - the short ref (SHA) for the commit referenced by that latest tag: "g975b" 50 | - an indication that there are uncommitted changes: "dirty" (or absent) 51 | 52 | ## The Two-Rule Method 53 | 54 | This build hook creates `the computed version` from these four "ambient" values by applying two rules: 55 | 1. when the "ahead" count is 0, and the repo is not dirty, `the computed version` will just be the latest tag (eg: `1.0.4`) 56 | 2. when the "ahead" count is non-zero, or the repo is dirty, `the computed version` will be the tag suffixed with `---SNAPSHOT`, e.g. `1.0.4-3-g975b-SNAPSHOT` 57 | 58 | ***Note #1:*** there is a configuration option to ignore `dirty` state. See the Configuration section below. 59 | 60 | ***Note #2:*** only part of the latest tag is used (just `1.0.4`, not the full string `v1.0.4`) but that's explained in the next section. 61 | 62 | ## The Latest Tag 63 | 64 | So far, we have said `the computed version` is created using the "latest tag". While that is often true, it is not the whole story, which is acually as follows: 65 | 1. what's used is the "latest version tag" found in the commit history ("latest version tag" vs "latest tag") 66 | 2. where a "version tag" is a tag with a specific textual structure 67 | 3. by default, that textual structure must match the regex: `#"^v(\d+\.\d+\.\d+)$"` 68 | 4. so, one of these "version tags" might look like: `v1.2.3` (the string `"v"` followed by a semver, `"N.N.N"`) 69 | 5. tags which do not match the regex are ignored (which means you can use tags for other purposes, not just for nominating versions) 70 | 6. you can override this default regex with one of your own which will recognise an alternative textual structure (see how below) 71 | 7. you'll notice that the regex has a capturing group which extracts just the semver part: "N.N.N". If you provide your own regex, it must contain a single capturing group which isolates that part of the tag to be used in `the computed version`. 72 | 73 | So, this build hook will traverse backwards through the history of the current commit looking for a tag which has the right structure (matches the regex), and when it finds one, it is THAT tag which is used to create `the computed version` - it is that tag against which the "ahead count" will be calculated, etc. 74 | 75 | ## Sharp Edges 76 | 77 | Please be aware of the following: 78 | - if no matching tag is found then `the computed version` will be `git-version-tag-not-found` 79 | - this build hook obtains the "ambient git context" by shelling out to the `git` executable. If this executable is not in the PATH, then you'll see messages on `stderr` and `the computed version` will be `git-command-not-found` 80 | 81 | ## The Two Steps 82 | 83 | The two-step narrative presented above says this build hook: 84 | 1. creates `the computed version` 85 | 2. replaces a placeholder string within build configuration(s) with `the computed version` 86 | 87 | While that's true, it is a simplification. The real steps are: 88 | 1. this build hook computes **four** build-time values, of which `the computed version` is just one 89 | 2. this build hook will perform a search and replace across ***all the `EDN`*** in 90 | the current build's configuration block, looking for four special string values and, where they are found, it will replace them with the associated computed value from step 1. 91 | 92 | So, the special keyword :shadow-git-inject/version will be replaced ***anywhere*** it is found within the build configuration EDN. 93 | 94 | When you consider this second step, keep in mind that this build hook runs 95 | very early in the shadow-cljs build pipeline at the `configure` step. 96 | 97 | The four special keywords supported - referred to as `substitution keys` - are: 98 | 99 | 100 | | substitution key | example replacement | 101 | |----------------------------------------|------------------------------| 102 | | :shadow-git-inject/version | "12.4.1-2-453a730-SNAPSHOT" | 103 | | :shadow-git-inject/build-iso-date-time | "2019-11-18T00:05:02.273361" | 104 | | :shadow-git-inject/build-iso-date-week | "2019-W47-2" | 105 | | :shadow-git-inject/user-name | "Isaac" | 106 | 107 | For performance, only certain parts of the shadow-cljs configuration are processed: 108 | 109 | - `[:builds :* :closure-defines]` 110 | - `[:builds :* :compiler-options :closure-defines]` 111 | - `[:builds :* :dev :closure-defines]` 112 | - `[:builds :* :dev :compiler-options :closure-defines]` 113 | - `[:builds :* :release :closure-defines]` 114 | - `[:builds :* :release :compiler-options :closure-defines]` 115 | 116 | If you have a use case for injecting values in a part of the config that is not listed above, please raise an issue. 117 | 118 | ## Embedding Build-Time Values In Your App 119 | 120 | This build hook provides a way 121 | to embed any of these four build-time values into our ClojureScript application. 122 | This is often a very useful outcome - these values are useful at runtime for display and logging purposes. And it can 123 | be achieved in an automated, DRY way. 124 | 125 | The trick is to place the substitution keys into specific places within the build configuration - ones which control 126 | the actions of the ClojureScript compiler. We want to take advantage of the [`:closure-defines` feature](https://clojurescript.org/reference/compiler-options#closure-defines) feature of the ClojureScript complier which permits us to "set" values for `defs` at compile time. 127 | 128 | Below, the Annotated Example section demonstrates how to achieve this outcome using shadow-cljs. 129 | 130 | 131 | ## Configuration 132 | 133 | A map of configuration options can, optionally, be added to the build configuration via the key `:git-inject`, like this: 134 | 135 | ```clj 136 | {:builds {:my-app {:target :browser 137 | 138 | :git-inject { ... } ;; a map of configuration options 139 | }}} 140 | ``` 141 | 142 | The two configuration options are: 143 | - `:ignore-dirty?` 144 | - `:version-pattern` 145 | 146 | #### :ignore-dirty? 147 | 148 | A boolean value which specifies if the dirty state of the repo should be ignored when calculating the version. 149 | 150 | Defaults to `false`. 151 | 152 | Can be supplied as an explicit boolean or via an environment variable as the string "true" or "false". 153 | 154 | ```clj 155 | :git-inject { 156 | :ignore-dirty? true 157 | } 158 | ``` 159 | OR 160 | ```clj 161 | :git-inject { 162 | ;; Will only be true if IGNORE_DIRTY environment variable is the string "true" 163 | ;; If the environment variable is not found, defaults to "false" 164 | :ignore-dirty? #shadow/env "IGNORE_DIRTY" 165 | } 166 | ``` 167 | 168 | #### :version-pattern 169 | 170 | A string containing the regex which is used to differentiate between `version tags` and other `tags`. If the regex 171 | matches a tag, then that tag is assumed to be a `version tag`, otherwise the tag will be ignored. 172 | 173 | Defaults to `"^v(\\d+\\.\\d+\\.\\d+)$"` which matches tags like `v1.0.0` and `v12.3.99`. 174 | 175 | When designing the textual structure for your "version tags", remember that 176 | git tags are git references and that there are rules about well formedness. 177 | For example, you can't have a ":" in a tag. See https://git-scm.com/docs/git-check-ref-format 178 | 179 | The regex you supply has two jobs: 180 | 1. to "match" version tags 181 | 2. to return one capturing group which extracts the text within the tag which is to 182 | be used as the version itself. In the example below, the regex will match the tag "version/1.2.3" 183 | but it will also capture the "1.2.3" part and it is THAT part which will be used in the computed version. 184 | 185 | ```clj 186 | :git-inject { 187 | :version-pattern "^version/(.*)$" 188 | } 189 | ``` 190 | 191 | **Note #1:** This build hook uses [`re-pattern`](https://clojuredocs.org/clojure.core/re-pattern) to turn the string into a regex. 192 | 193 | **Note #2:** Because you supply a string and not a regex, be careful to use `\\` where normally you could just use `\` in a regex. 194 | 195 | **Note #3:** Why a string and not a regex? Because EDN doesn't accomodate regex. 196 | 197 | ## An Annotated Example 198 | 199 | Here's how to write your `shadow-cljs.edn` ... 200 | 201 | ```clojure 202 | {:dependencies [[day8/shadow-git-inject "0.0.4"]] ;; <--- include hook dependency (see https://clojars.org/day8/shadow-git-inject) 203 | 204 | :builds {:app {:target :browser 205 | 206 | :build-hooks 207 | [(shadow-git-inject.core/hook)] ;; <--- you must include this build hook 208 | 209 | ;; Below is an example of how to 210 | ;; combine this build hook with a `:clojure-define` in order to 211 | ;; inject build-time values into your application, for later run-time use. 212 | ;; 213 | ;; You'll notice the use of the substitution key :shadow-git-inject/version. 214 | ;; At build time, this build hook will replace that keyword with `the computed version`. 215 | ;; In turn, that value is used within a `:clojure-define` to bind it 216 | ;; to a var, via a `def` in your code (called `version` within the namespace `some.namespace`). 217 | :compiler-options 218 | {:closure-defines {some.namespace/version :shadow-git-inject/version}} 219 | 220 | ;; Optional - see the `Configuration` section for explanation 221 | :git-inject 222 | {:version-pattern "^version/(.*)$" 223 | :ignore-dirty? true}}}} 224 | ``` 225 | 226 | 227 | ## License 228 | 229 | Copyright © 2021 Mike Thompson 230 | 231 | Derived from lein-git-inject © 2020 Mike Thompson 232 | 233 | Derived from cuddlefish © 2018 Reid "arrdem" McKenzie 234 | 235 | Derived from lein-git-version © 2017 Reid "arrdem" McKenzie 236 | 237 | Derived from lein-git-version © 2016 Colin Steele 238 | 239 | Derived from lein-git-version © 2011 Michał Marczyk 240 | 241 | Distributed under the Eclipse Public License, the same as Clojure. 242 | 243 | 244 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | --------------------------------------------------------------------------------