├── .gitignore ├── CHANGES.md ├── README.md ├── lein-template ├── project.clj └── src │ └── leiningen │ └── new │ ├── lein_clr.clj │ └── lein_clr │ ├── README.md │ ├── core.clj │ ├── core_test.clj │ ├── gitignore │ ├── intro.md │ └── project.clj ├── plugin ├── project.clj └── src │ └── leiningen │ ├── clr.clj │ └── clr │ └── internal.clj ├── project.clj └── sample.project.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | */target 3 | /lib 4 | /classes 5 | /checkouts 6 | pom.xml 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes and TODO 2 | 3 | 4 | ## TODO 5 | 6 | * CLR re-implementation (of Leiningen features) 7 | * eval-in-project 8 | * bultitude(??) 9 | * enable plugins written in ClojureCLR 10 | * Tasks 11 | * test (test selector support - doable??) 12 | * Script support 13 | * Executing scripts _a la_ lein-exec 14 | * `compile` as implicit as well as named task invokable at command line 15 | * Pre-compile and Post-compile commands; to compile C#, F# etc. sources 16 | * Load compiled assemblies into assembly search path 17 | 18 | 19 | ## 2014-Sep-19 / 0.2.2 20 | 21 | * Omit JAR `:dependencies` from source dependencies (for ClojureCLR 1.6) - Commit SHA: 6c6f60ea9f6c62dbbc30d17d1d0132552070e5c6 22 | 23 | 24 | ## 2013-Sep-22 / 0.2.1 25 | 26 | * Honor `[:clr :unchecked-math]` 27 | * Honor `[:clr :warn-on-reflection]` and `:warn-on-reflection` 28 | * `deps` as implicit as well as a named task invokable at command line 29 | * Project config support (`:clr` key in `project.clj`) 30 | * Command resolution 31 | * Support for optional search-within env-var value, e.g. `[?PATH "foo.exe"]` 32 | 33 | 34 | ## 2012-Nov-19 / 0.2.0 35 | 36 | * Project config support (`:clr` key in `project.clj`) 37 | * Support for `:assembly-paths` 38 | * transparently calls `assembly-load-from` for matching assemblies 39 | * Command resolution 40 | * Support searching within env-var value, e.g. `[*PATH "foo.exe"]` 41 | * Command templates `:cmd-templates` 42 | * Dependency support 43 | * Maven dependencies on CLOJURE_LOAD_PATH 44 | * Command-based dependencies (via NuGet etc.) -- `:deps-cmds` 45 | * Assembly deps regex `:assembly-deps-regex` to match versions/types 46 | * transparently calls `assembly-load-from` for matching assemblies 47 | * Fixes for `compile` task 48 | * sample.project.clj with config options 49 | * Updated Leiningen-project template 50 | 51 | 52 | ## 2012-Oct-28 / 0.1.0 53 | 54 | * Tasks 55 | * clean 56 | * compile 57 | * repl 58 | * run 59 | * test (without test-selector support) 60 | * Project config support (`:clr` key in `project.clj`) 61 | * Configurable Clojure executable names: `:compile-cmd` `:main-cmd` 62 | * Mono support: `:compile-cmd` and `:main-cmd` 63 | * External libraries support: `:load-paths` 64 | * Support for multiple ClojureCLR versions via env-var lookup 65 | * Leiningen-project template to generate skeleton project 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lein-clr 2 | 3 | A Leiningen plugin to automate build tasks for ClojureCLR projects. 4 | 5 | Leiningen 2 is required to use this plugin. You can use it for both .NET and Mono. 6 | 7 | *Important:* This plugin is in _Beta_. Please report bugs, share ideas, comments etc. 8 | 9 | 10 | ## Installation 11 | 12 | Install as a project level plugin in `project.clj`: 13 | 14 | ```clojure 15 | :plugins [[lein-clr "0.2.2"]] 16 | ``` 17 | 18 | **Note:** `lein-clr` redefines the environment variables `CLOJURE_LOAD_PATH` 19 | and `CLOJURE_COMPILE_PATH` internally ignoring their original values. 20 | 21 | 22 | ## Usage 23 | 24 | The _lein-clr_ plugin needs a `:clr` key in `project.clj` for CLR related details. See [sample.project.clj](/kumarshantanu/lein-clr/sample.project.clj) for examples. The sub-sections below show how to get started. 25 | 26 | ### Quickstart in 3 steps -- requires `curl`/`wget` and `unzip` on `PATH` 27 | 28 | (Assuming you are on Windows with a recent version of the .NET framework or Mono. 29 | It should work on Unix-like systems using Mono if available in PATH. If you do 30 | not have `curl`/`wget` and `unzip` on PATH, consider _Quickstart in 4 steps_.) 31 | 32 | 1. Create a new Leiningen project 33 | 34 | ```batch 35 | C:\work> lein new lein-clr foo 36 | C:\work> cd foo 37 | ``` 38 | 39 | 2. The default `project.clj` does not enable automatic download of ClojureCLR; 40 | enable that by editing `project.clj` under `:clr` as follows: 41 | * Uncomment `[:wget :clj-zip :clj-url]` and `[:unzip "../clj" :clj-zip]` 42 | under `:deps-cmds`. 43 | * Under `:main-cmd` and `:compile-cmd` replace `:clj-exe` with `:clj-dep`. 44 | 45 | 3. Run the build tasks 46 | 47 | ```batch 48 | C:\work\foo> lein clr test 49 | C:\work\foo> lein clr run -m foo.core 50 | C:\work=foo> lein clr -v compile foo.core 51 | ``` 52 | 53 | -------- 54 | 55 | ### Quickstart in 4 steps 56 | 57 | (Assuming you are on Windows with a recent version of the .NET framework or 58 | Mono. It should work on Unix-like systems using Mono if available in `PATH`.) 59 | 60 | 1. Download a recent ClojureCLR binary package from here: 61 | 62 | http://sourceforge.net/projects/clojureclr/files/ 63 | 64 | 2. Uncompress it into a suitable directory and define an environment variable 65 | pointing to that directory location, e.g: 66 | 67 | `CLJCLR14_40=C:\clojure-clr-1.4.1-Debug-40` 68 | 69 | 3. Create a new Leiningen project 70 | 71 | ```batch 72 | C:\work> lein new lein-clr foo 73 | C:\work> cd foo 74 | ``` 75 | 76 | 4. Try the build tasks 77 | 78 | ```batch 79 | C:\work\foo> lein clr test 80 | C:\work\foo> lein clr run -m foo.core 81 | C:\work\foo> lein clr -v compile 82 | ``` 83 | 84 | ### Build tasks 85 | 86 | You can carry out a number of build tasks for ClojureCLR projects 87 | using [Microsoft .NET](http://en.wikipedia.org/wiki/.NET_Framework) 88 | or [Mono](http://www.mono-project.com). A synopsis of the tasks: 89 | 90 | ```bash 91 | lein clr [-v] clean 92 | lein clr [-v] compile 93 | lein clr [-v] help 94 | lein clr [-v] repl 95 | lein clr [-v] run [-m ns-having-main] [arg1 [arg2] ...] 96 | lein clr [-v] test [test-ns1 [test-ns2] ...] 97 | ``` 98 | 99 | ### Project configuration 100 | 101 | `lein-clr` uses some regular attributes from `project.clj` for build tasks. 102 | Besides, there are some specific attributes you can refer to in the 103 | `sample.project.clj` file in this repo. 104 | 105 | 106 | ## Getting in touch 107 | 108 | Clojure discussion group: https://groups.google.com/group/clojure 109 | 110 | Leiningen discussion group: https://groups.google.com/group/leiningen 111 | 112 | With me: By [Email](mailto:kumar.shantanu@gmail.com) 113 | or on Twitter: [@kumarshantanu](https://twitter.com/kumarshantanu) 114 | 115 | 116 | ## License 117 | 118 | Copyright © 2012-2014 Shantanu Kumar 119 | 120 | Distributed under the Eclipse Public License, the same as Clojure. 121 | -------------------------------------------------------------------------------- /lein-template/project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-clr/lein-template "0.2.2" 2 | :description "Template for ClojureCLR project built by lein-clr" 3 | :url "https://github.com/kumarshantanu/lein-clr" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :eval-in-leiningen true) 7 | -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.new.lein-clr 2 | (:use [leiningen.new.templates :only [renderer name-to-path ->files]])) 3 | 4 | (def render (renderer "lein-clr")) 5 | 6 | (defn lein-clr 7 | "A skeleton project to use with lein-clr" 8 | [name] 9 | (let [data {:name name 10 | :sanitized (name-to-path name)}] 11 | (println "Generating a skeleton ClojureCLR project" 12 | (str name "...")) 13 | (->files data 14 | ["src/{{sanitized}}/core.clj" (render "core.clj" data)] 15 | ["test/{{sanitized}}/core_test.clj" (render "core_test.clj" data)] 16 | ["doc/intro.md" (render "intro.md" data)] 17 | ["project.clj" (render "project.clj" data)] 18 | ["README.md" (render "README.md" data)] 19 | [".gitignore" (render "gitignore" data)]))) 20 | -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | A ClojureCLR library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2012 FIXME 12 | 13 | Distributed under the Eclipse Public License, the same as Clojure. -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr/core.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.core) 2 | 3 | (defn foo 4 | "I don't do a whole lot." 5 | [x] 6 | (println x "Hello, World!")) 7 | 8 | (defn -main 9 | [& args] 10 | (apply println "Received args:" args)) -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns {{name}}.core-test 2 | (:use clojure.test 3 | {{name}}.core)) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr/gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | *.jar 7 | *.class 8 | *.dll 9 | *.pdb 10 | *.exe 11 | .lein-deps-sum 12 | .lein-failures 13 | .lein-plugins -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to {{name}} 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) -------------------------------------------------------------------------------- /lein-template/src/leiningen/new/lein_clr/project.clj: -------------------------------------------------------------------------------- 1 | (defproject {{name}} "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [] 7 | :warn-on-reflection true 8 | :min-lein-version "2.0.0" 9 | :plugins [[lein-clr "0.2.2"]] 10 | :clr {:cmd-templates {:clj-exe [[?PATH "mono"] [CLJCLR14_40 %1]] 11 | :clj-dep [[?PATH "mono"] ["target/clr/clj/Debug 4.0" %1]] 12 | :clj-url "http://sourceforge.net/projects/clojureclr/files/clojure-clr-1.4.1-Debug-4.0.zip/download" 13 | :clj-zip "clojure-clr-1.4.1-Debug-4.0.zip" 14 | :curl ["curl" "--insecure" "-f" "-L" "-o" %1 %2] 15 | :nuget-ver [[?PATH "mono"] [*PATH "nuget.exe"] "install" %1 "-Version" %2] 16 | :nuget-any [[?PATH "mono"] [*PATH "nuget.exe"] "install" %1] 17 | :unzip ["unzip" "-d" %1 %2] 18 | :wget ["wget" "--no-check-certificate" "--no-clobber" "-O" %1 %2]} 19 | ;; for automatic download/unzip of ClojureCLR, 20 | ;; 1. make sure you have curl or wget installed and on PATH, 21 | ;; 2. uncomment deps in :deps-cmds, and 22 | ;; 3. use :clj-dep instead of :clj-exe in :main-cmd and :compile-cmd 23 | :deps-cmds [; [:wget :clj-zip :clj-url] ; edit to use :curl instead of :wget 24 | ; [:unzip "../clj" :clj-zip] 25 | ] 26 | :main-cmd [:clj-exe "Clojure.Main.exe"] 27 | :compile-cmd [:clj-exe "Clojure.Compile.exe"]}) 28 | -------------------------------------------------------------------------------- /plugin/project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-clr "0.2.2" 2 | :description "Leiningen plugin to automate build tasks for ClojureCLR projects" 3 | :url "https://github.com/kumarshantanu/lein-clr" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :min-lein-version "2.0.0" 7 | :eval-in-leiningen true) 8 | -------------------------------------------------------------------------------- /plugin/src/leiningen/clr.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.clr 2 | (:require [clojure.pprint :as pp] 3 | [clojure.string :as str] 4 | [clojure.java.io :as io] 5 | [leiningen.clr.internal :as in] 6 | [leiningen.core.classpath :as lc]) 7 | (:import (java.io Reader BufferedReader File InputStream InputStreamReader 8 | OutputStream Writer) 9 | (java.util Map))) 10 | 11 | 12 | ;; ===== Project keys ===== 13 | 14 | (def pk-target-path [:clr :target-path]) 15 | (def pk-cmd-templates [:clr :cmd-templates]) 16 | (def pk-compile-cmd [:clr :compile-cmd]) 17 | (def pk-main-cmd [:clr :main-cmd]) 18 | (def pk-assembly-paths [:clr :assembly-paths]) 19 | (def pk-deps-cmds [:clr :deps-cmds]) 20 | (def pk-assembly-deps-regex [:clr :assembly-deps-regex]) 21 | (def pk-load-paths [:clr :load-paths]) 22 | (def pk-unchecked-math [:clr :unchecked-math]) 23 | (def pk-warn-on-reflection [:clr :warn-on-reflection]) 24 | 25 | (def pk-aot [:clr :aot]) 26 | 27 | 28 | ;; ===== Target paths ===== 29 | 30 | (defn target-path 31 | [project] 32 | (or (get-in project pk-target-path) 33 | (str (:target-path project) File/separator "clr"))) 34 | 35 | 36 | (defn target-bin-path 37 | [project] 38 | (str (target-path project) File/separator "bin")) 39 | 40 | 41 | (defn target-lib-path 42 | [project] 43 | (str (target-path project) File/separator "lib")) 44 | 45 | 46 | (defn target-src-path 47 | [project] 48 | (str (target-path project) File/separator "src")) 49 | 50 | 51 | ;; ===== Project keys ===== 52 | 53 | (defn cmd-templates 54 | [project] 55 | (get-in project pk-cmd-templates)) 56 | 57 | 58 | (defn proj-key-cmd 59 | "Return a sequence of command and arguments" 60 | [proj-key default-value project] 61 | (or (when-let [cmd (get-in project proj-key)] 62 | (assert (vector? cmd)) 63 | (in/resolve-path cmd (cmd-templates project))) 64 | default-value)) 65 | 66 | 67 | (defn default-cmd 68 | [^String cmd] {:pre [(string? cmd)]} 69 | (let [^String os-name (System/getProperty "os.name")] 70 | (if (and os-name (.startsWith ^String os-name "Windows")) 71 | [cmd] 72 | ["mono" (or (in/which cmd) cmd)]))) 73 | 74 | 75 | (def clj-compile-cmd (partial proj-key-cmd pk-compile-cmd 76 | (default-cmd "Clojure.Compile.exe"))) 77 | 78 | 79 | (def clj-main-cmd (partial proj-key-cmd pk-main-cmd 80 | (default-cmd "Clojure.Main.exe"))) 81 | 82 | 83 | (defn all-load-paths 84 | [project] 85 | (let [load-paths (get-in project pk-load-paths)] 86 | (when load-paths 87 | (in/warn "[:clr :load-paths] is deprecated and will be discontinued soon. Consider using :resource-paths")) 88 | (->> load-paths 89 | (map #(in/resolve-path % (cmd-templates project))) 90 | (concat (lc/get-classpath project) 91 | [(target-src-path project)])))) 92 | 93 | 94 | (defn aot-namespaces 95 | [project] 96 | (let [aot-nses (or (get-in project pk-aot) 97 | (:aot project)) 98 | all-nses (mapcat in/scan-namespaces (all-load-paths project))] 99 | (mapcat (fn [each] 100 | (if (instance? java.util.regex.Pattern each) 101 | (filter (partial re-matches each) all-nses) 102 | [(str each)])) 103 | aot-nses))) 104 | 105 | 106 | (defn assembly-search-paths 107 | [project] 108 | (->> (get-in project pk-assembly-paths) 109 | (map in/as-vector) 110 | (concat [[(target-lib-path project) 111 | (get-in project pk-assembly-deps-regex)]]) 112 | (mapcat in/filter-assembly-paths))) 113 | 114 | 115 | (defn get-eval-string 116 | [project] 117 | (str (when (get-in project pk-unchecked-math) 118 | "(set! *unchecked-math* true)") 119 | (when (or (get-in project pk-warn-on-reflection) 120 | (:warn-on-reflection project)) 121 | "(set! *warn-on-reflection* true)"))) 122 | 123 | 124 | (defn asm-load-init 125 | [project init-file] 126 | (in/spit-assembly-load-instruction 127 | init-file 128 | (assembly-search-paths project)) 129 | (spit init-file (str \newline (get-eval-string project)) :append true) 130 | init-file) 131 | 132 | 133 | (defn configure-compile-env 134 | [project ^Map process-env load-paths] 135 | (in/configure-load-path process-env load-paths) 136 | (in/configure-compile-path process-env (target-bin-path project)) 137 | (->> (get-in project pk-unchecked-math) 138 | (in/configure-unchecked-math process-env)) 139 | (->> (:warn-on-reflection project) 140 | (or (get-in project pk-warn-on-reflection)) 141 | (in/configure-warn-on-reflection process-env))) 142 | 143 | 144 | ;;; ========== Tasks ========== 145 | 146 | 147 | (defn task-clean 148 | [project] 149 | (let [tp (target-path project)] 150 | (in/verbose "Recursively deleting directory: " tp) 151 | (in/rm-rf tp))) 152 | 153 | 154 | (defn task-deps 155 | "Fetch dependencies. Do not fetch if already fetched earlier." 156 | [project] 157 | (let [lib (target-lib-path project) 158 | src (target-src-path project) 159 | src-p #"^((?!project\.clj|META\-INF|.*\.class|cljs/|.*\.cljs).*)$" 160 | cmds (get-in project pk-deps-cmds)] 161 | (if-not (.exists (File. lib)) 162 | (do 163 | (in/verbose "Making sure" lib "exists") 164 | (in/mkdir-p lib) 165 | (in/verbose "Fetching dependencies" cmds) 166 | (->> cmds 167 | (map #(in/resolve-path % (cmd-templates project))) 168 | (map #(in/run-cmd % lib)) 169 | dorun)) 170 | (in/verbose "Not fetching dependencies. Run `lein clean` to re-fetch.")))) 171 | 172 | 173 | (defn task-compile 174 | [project namespaces] 175 | (task-deps project) 176 | (let [allp (all-load-paths project) 177 | srcp (concat (:source-paths project) (:test-paths project)) 178 | nses (cond 179 | (empty? namespaces) (aot-namespaces project) 180 | (and (= 1 (count namespaces)) 181 | (= ":all" (first namespaces))) (mapcat in/scan-namespaces srcp) 182 | :otherwise namespaces) 183 | exec (concat (clj-compile-cmd project) nses)] 184 | (in/with-process-builder pb (:root project) exec 185 | (configure-compile-env project (.environment ^ProcessBuilder pb) allp) 186 | (apply in/verbose "Running: " (map pr-str exec)) 187 | (in/run-process pb)))) 188 | 189 | 190 | (defn task-help 191 | [] 192 | (println " 193 | Available tasks: 194 | clean delete files generated by lein-clr 195 | compile compile clj source to .dll .exe .pdb/.mdb files; available switch :all 196 | deps fetch project dependencies (called implicitly by other tasks) 197 | help show this help screen 198 | repl load a REPL with sources into CLOJURE_LOAD_PATH 199 | run run a namespace having `-main` function 200 | test run tests in specified/all test namespaces 201 | ")) 202 | 203 | 204 | (defn task-repl 205 | [project] 206 | (task-deps project) 207 | (let [init-file (asm-load-init project (in/get-temp-file)) 208 | allp (all-load-paths project) 209 | _ (in/verbose-init-with init-file) 210 | exec (concat (clj-main-cmd project) ["-i" init-file "-e" (get-eval-string project) "-r"])] 211 | (in/with-process-builder pb (:root project) exec 212 | (in/configure-load-path (.environment ^ProcessBuilder pb) allp) 213 | (apply in/verbose "Running:" (map pr-str exec)) 214 | (in/run-process pb :pipe-input)))) 215 | 216 | 217 | (def no-main "No :main namespace specified in project.clj.") 218 | (def no-ns "Option -m requires a namespace argument.") 219 | 220 | (defn parse-run-args 221 | "Return a vector where first elem is the main ns, followed by remaining args" 222 | [project args] 223 | (let [proj-ns #(or (:main project) (in/exit-error 1 no-main)) 224 | user-ns #(or (second args) (in/exit-error 1 no-ns))] 225 | (cond 226 | (empty? args) [(str (proj-ns))] 227 | (= (first args) "--") [(str (proj-ns)) (rest args)] 228 | (= (first args) "-m") [(user-ns) (nthrest args 2)] 229 | :otherwise [(str (proj-ns)) args]))) 230 | 231 | 232 | (defn task-run 233 | [project args] 234 | (task-deps project) 235 | (let [init-file (asm-load-init project (in/get-temp-file)) 236 | allp (all-load-paths project) 237 | [rns args] (parse-run-args project args) 238 | _ (in/verbose-init-with init-file) 239 | r-ex (concat (clj-main-cmd project) 240 | ["-i" init-file "-e" (get-eval-string project) 241 | "-m" rns] args)] 242 | ;; run the namespace (r-ex) 243 | (in/with-process-builder pb (:root project) r-ex 244 | (in/configure-load-path (.environment ^ProcessBuilder pb) allp) 245 | (apply in/verbose "Running: " (map pr-str r-ex)) 246 | (in/run-process pb)))) 247 | 248 | 249 | (defn task-test 250 | [project namespaces] 251 | (task-deps project) 252 | (let [init-file (asm-load-init project (in/get-temp-file)) 253 | allp (all-load-paths project) 254 | nses (mapcat in/scan-namespaces (:test-paths project)) 255 | rtns (if (seq namespaces) namespaces nses) 256 | nstr (str/join " " (map (partial str "'") rtns)) 257 | _ (in/spit-require-ns init-file rtns) 258 | _ (in/verbose-init-with init-file) 259 | expr (format "(use 'clojure.test) (run-tests %s)" nstr) 260 | exec (concat (clj-main-cmd project) 261 | ["-i" init-file "-e" (str (get-eval-string project) 262 | expr)])] 263 | ;; run the tests 264 | (println "Running ClojureCLR tests") 265 | (in/with-process-builder pb (:root project) exec 266 | (in/configure-load-path (.environment ^ProcessBuilder pb) allp) 267 | (apply in/verbose "Running: " (map pr-str exec)) 268 | (in/run-process pb)))) 269 | 270 | 271 | (defmacro try-pst 272 | [& body] 273 | `(try ~@body 274 | (catch Throwable t# 275 | (.printStackTrace t#)))) 276 | 277 | 278 | (defn clr 279 | "Automate build tasks for ClojureCLR projects 280 | 281 | For more information, run the below command: 282 | 283 | lein clr help" 284 | [project & [task & args]] 285 | (case task 286 | "clean" (task-clean project) 287 | "compile" (try-pst (task-compile project args)) 288 | "deps" (try-pst (task-deps project)) 289 | "help" (task-help) 290 | "repl" (try-pst (task-repl project)) 291 | "run" (try-pst (task-run project args)) 292 | "test" (try-pst (task-test project args)) 293 | "-v" (binding [in/*verbose* true] (apply clr project args)) 294 | (do (println "No such task: " (pr-str task)) 295 | (task-help)))) 296 | -------------------------------------------------------------------------------- /plugin/src/leiningen/clr/internal.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.clr.internal 2 | (:require [clojure.pprint :as pp] 3 | [clojure.string :as str] 4 | [clojure.java.io :as io] 5 | [leiningen.core.main :as lm]) 6 | (:import (java.io File Reader BufferedReader InputStream InputStreamReader 7 | OutputStream Writer) 8 | (java.util Enumeration Map) 9 | (java.util.zip ZipEntry ZipFile))) 10 | 11 | 12 | (def ^:dynamic *verbose* false) 13 | 14 | (defn echo [x] 15 | (println "[ECHO]" x (pr-str)) x) 16 | 17 | 18 | (defn echo-> 19 | [x & args] 20 | (apply println "[ECHO]" (concat args [x])) 21 | x) 22 | 23 | 24 | (defn echo->> 25 | [y & args] 26 | (let [msgs (drop-last (cons y args)) 27 | x (last args)] 28 | (apply echo-> x msgs))) 29 | 30 | 31 | (defn verbose 32 | [x & args] 33 | (when *verbose* 34 | (apply println "[lein-clr] [DEBUG]" x args)) 35 | (flush)) 36 | 37 | 38 | (defn warn 39 | [x & args] 40 | (apply println "\n[lein-clr] [WARNING!]" x args "\n") 41 | (flush)) 42 | 43 | 44 | (defn exit-error 45 | ([code msg & more] {:pre [(pos? code)]} 46 | (binding [*out* *err*] 47 | (apply println "\n[lein-clr] [ERROR]" msg more) 48 | (flush)) 49 | (lm/abort code)) 50 | ([code] {:pre [(pos? code)]} 51 | (lm/abort code))) 52 | 53 | 54 | (defn echo 55 | [x] 56 | (println "[lein-clr] [--ECHO--]" x) 57 | (flush) 58 | x) 59 | 60 | 61 | (defn sleep 62 | [millis] 63 | (try (Thread/sleep millis) 64 | (catch InterruptedException _ 65 | (.interrupt (Thread/currentThread))))) 66 | 67 | 68 | (def IDLE 10) 69 | (def EOF -1) 70 | 71 | 72 | (defn pipe-output 73 | [quit? ^InputStream out ^Writer dest] 74 | (let [rdr (BufferedReader. (io/reader out))] 75 | (loop [] 76 | (when 77 | (if-not (.ready rdr) 78 | (do (sleep IDLE) 79 | (or (.ready rdr) (not @quit?))) 80 | (let [i (.read ^BufferedReader rdr)] 81 | (when-not (= i EOF) 82 | (.write ^Writer dest i) 83 | (.flush dest) 84 | true))) 85 | (recur))) 86 | (verbose "Output closed: " dest))) 87 | 88 | 89 | (defn pipe-input 90 | [quit? ^OutputStream in ^Reader source] 91 | (let [con (BufferedReader. (io/reader source)) 92 | wtr (io/writer in)] 93 | (loop [i (.read ^BufferedReader con)] 94 | (when-not (or (= i EOF) @quit?) 95 | (.write wtr i) 96 | (.flush wtr) 97 | (when-not @quit? 98 | (recur (.read ^BufferedReader con))))) 99 | (.close in) 100 | (reset! quit? true) 101 | (verbose "Input closed: " source))) 102 | 103 | 104 | (defmacro futurex 105 | [& body] 106 | `(future 107 | (try ~@body 108 | (catch Exception e# 109 | (.printStackTrace e#))))) 110 | 111 | 112 | (defn run-process 113 | ([^ProcessBuilder process-builder pipe-input?] 114 | (let [^Process process (.start process-builder) 115 | quit? (atom false) 116 | e (futurex (pipe-output quit? (.getErrorStream process) *err*)) 117 | o (futurex (pipe-output quit? (.getInputStream process) *out*)) 118 | i (when pipe-input? 119 | (futurex (pipe-input quit? (.getOutputStream process) System/in)))] 120 | (let [exit (.waitFor process)] 121 | (reset! quit? true) 122 | (when i @i) 123 | (when (pos? exit) 124 | @e @o 125 | (lm/abort exit))))) 126 | ([^ProcessBuilder process-builder] 127 | (run-process process-builder false))) 128 | 129 | 130 | (defn scan-namespaces 131 | "Given a toplevel directory, recursively scan .clj files and return a 132 | collection of namespaces." 133 | ([toplevel-dir parent-ns] {:pre [(vector? parent-ns)]} 134 | (let [tl-dir (if (instance? File toplevel-dir) 135 | toplevel-dir 136 | (File. toplevel-dir)) 137 | clj-x? #(.endsWith ^String (.getName ^File %) ".clj") 138 | hyphen #(.replace ^String % \_ \-)] 139 | (mapcat (fn [^File each] 140 | (cond 141 | (.isDirectory each) (scan-namespaces each 142 | (->> (.getName each) 143 | hyphen 144 | (conj parent-ns))) 145 | (and (.isFile each) 146 | (clj-x? each)) (let [s (.getName each)] 147 | (vector (->> (subs s 0 (- (count s) 4)) 148 | hyphen 149 | (conj parent-ns) 150 | (str/join ".")))) 151 | :otherwise [])) 152 | (.listFiles ^File tl-dir)))) 153 | ([toplevel-dir] 154 | (scan-namespaces toplevel-dir []))) 155 | 156 | 157 | (defn as-file 158 | [x] 159 | (if (instance? File x) 160 | x 161 | (File. (str x)))) 162 | 163 | 164 | (defn mkdir-p 165 | [dir] 166 | (let [^File d (as-file dir)] 167 | (when-not (.exists d) 168 | (.mkdirs d)))) 169 | 170 | 171 | (defn rm-rf 172 | [dir] 173 | (let [^File d (as-file dir)] 174 | (when (.exists d) 175 | (when (.isDirectory d) 176 | (dorun (map #(if (.isDirectory %) (rm-rf %) (.delete %)) 177 | (.listFiles d)))) 178 | (.delete d)))) 179 | 180 | 181 | (defn which 182 | "Implementation of the `which` command for systems like `mono`. Return 183 | absolute path of the file, or nil if no valid file found." 184 | ([cmd path] {:pre [(string? cmd) 185 | (string? path)]} 186 | (let [tokens (.split ^String path File/pathSeparator) 187 | find-f (fn [dir-name] 188 | (let [f (-> (File. dir-name) 189 | (.getAbsolutePath) 190 | (str File/separator cmd) 191 | (File.))] 192 | (when (.isFile ^File f) 193 | (.getAbsolutePath ^File f))))] 194 | (some find-f tokens))) 195 | ([cmd] 196 | (when-let [path (.get ^Map (System/getenv) "PATH")] 197 | (which cmd path)))) 198 | 199 | 200 | (def CLOJURE_LOAD_PATH "CLOJURE_LOAD_PATH") 201 | (def CLOJURE_COMPILE_PATH "CLOJURE_COMPILE_PATH") 202 | (def CLOJURE_COMPILE_UNCHECKED_MATH "CLOJURE_COMPILE_UNCHECKED_MATH") 203 | (def CLOJURE_COMPILE_WARN_ON_REFLECTION "CLOJURE_COMPILE_WARN_ON_REFLECTION") 204 | 205 | 206 | (defn configure-load-path 207 | [^Map process-env paths] 208 | ;; include source paths into CLOJURE_LOAD_PATH 209 | (let [clp (str/join File/pathSeparator paths)] 210 | (.put ^Map process-env CLOJURE_LOAD_PATH clp) 211 | (verbose "Using CLOJURE_LOAD_PATH:" clp))) 212 | 213 | 214 | (defn configure-compile-path 215 | [^Map process-env target-path] {:pre [(string? target-path)]} 216 | ;; set CLOJURE_COMPILE_PATH to the target path 217 | (.put process-env CLOJURE_COMPILE_PATH target-path) 218 | (verbose "Using CLOJURE_COMPILE_PATH:" target-path) 219 | (mkdir-p target-path)) 220 | 221 | 222 | (defn configure-unchecked-math 223 | [^Map process-env flag] 224 | (when flag 225 | (.put process-env CLOJURE_COMPILE_UNCHECKED_MATH "true") 226 | (verbose "Using CLOJURE_COMPILE_UNCHECKED_MATH:" "true"))) 227 | 228 | 229 | (defn configure-warn-on-reflection 230 | [^Map process-env flag] 231 | (when flag 232 | (.put process-env CLOJURE_COMPILE_WARN_ON_REFLECTION "true") 233 | (verbose "Using CLOJURE_COMPILE_WARN_ON_REFLECTION:" "true"))) 234 | 235 | 236 | (defmacro with-process-builder 237 | [pb-symbol base-dir cmd-and-args & body] 238 | `(let [^ProcessBuilder ~pb-symbol (ProcessBuilder. ~cmd-and-args)] 239 | ;; set project-root 240 | (.directory ^ProcessBuilder ~pb-symbol (File. ~base-dir)) 241 | (verbose "Using base directory:" ~base-dir) 242 | ~@body)) 243 | 244 | 245 | (defn run-cmd 246 | [exec root] 247 | (verbose "Running" exec) 248 | (with-process-builder 249 | pb root exec 250 | (run-process pb))) 251 | 252 | 253 | (defn resolve-template 254 | [template args] 255 | (if (vector? template) 256 | (-> (fn [each] 257 | (cond 258 | ;; symbol 259 | (symbol? each) 260 | (let [[pc & digits :as sym] (name each)] 261 | (if (= \% pc) 262 | (let [num (if (seq digits) 263 | (Integer/parseInt (str/join digits)) 264 | 1)] 265 | (nth args (dec num))) 266 | each)) 267 | ;; vector 268 | (vector? each) 269 | (resolve-template each args) 270 | ;; fallback 271 | :otherwise 272 | each)) 273 | (map template) 274 | vec) 275 | ;; fallback 276 | template)) 277 | 278 | 279 | (defn resolve-path-str 280 | "Convert path to string and return it." 281 | [f] 282 | (cond 283 | (symbol? f) (or (System/getenv (name f)) 284 | (exit-error 1 "No such environment variable:" (name f))) 285 | (string? f) f 286 | (vector? f) (str/join File/separator 287 | (map resolve-path-str f)) 288 | :otherwise (exit-error 1 "Expected string/symbol/vector, found" 289 | (pr-str f)))) 290 | 291 | 292 | (defn path-search 293 | [[path file]] 294 | (let [file (resolve-path-str (vector file)) 295 | [p & r] (name path) 296 | path (resolve-path-str (-> r str/join symbol))] 297 | (if-let [w (which file path)] 298 | w 299 | (when-not (= p \?) 300 | (exit-error 1 "Cannot locate" file "in" path))))) 301 | 302 | 303 | (defn resolve-path 304 | "If you supply a vector of args, you get back a vector. 305 | See also: 306 | resolve-path-str" 307 | [f template-map] 308 | (let [assert-tkey (fn [needle] 309 | (when-not (contains? template-map needle) 310 | (exit-error 1 "Template key" needle "not found in" 311 | (pr-str template-map)))) 312 | templ-subst (fn [f] 313 | (let [left (take-while (comp not keyword?) f) 314 | needle (nth f (count left)) 315 | right (->> (drop (inc (count left)) f) 316 | (map #(resolve-path %1 template-map)))] 317 | (assert-tkey needle) 318 | (resolve-path 319 | (->> (resolve-template (get template-map needle) right) 320 | (concat left) 321 | vec) 322 | template-map)))] 323 | (cond 324 | (symbol? f) (or (System/getenv (name f)) 325 | (exit-error 1 "No such environment variable:" (name f))) 326 | (keyword? f) (do (assert-tkey f) 327 | (get template-map f)) 328 | (string? f) f 329 | (vector? f) (cond 330 | ;; path search 331 | (and (= 2 (count f)) 332 | (symbol? (first f)) 333 | (let [[p1 & the-name] (seq (name (first f)))] 334 | (and (#{\* \?} p1) (seq the-name)))) 335 | (path-search f) 336 | ;; template substitution 337 | (some keyword? f) 338 | (templ-subst f) 339 | ;; fallback (regular) resolution 340 | :otherwise 341 | (->> f 342 | (map #(resolve-path % template-map)) 343 | (keep identity) 344 | (map resolve-path-str) 345 | flatten 346 | vec)) 347 | :otherwise (exit-error 1 "Expected string/symbol/vector, found" 348 | (pr-str f))))) 349 | 350 | 351 | (defn get-temp-file 352 | [] 353 | (let [f (File/createTempFile "lein-clr-" ".tmp")] 354 | (.deleteOnExit ^File f) 355 | (doto (.getAbsolutePath ^File f) 356 | (spit "")))) 357 | 358 | 359 | (defn as-vector 360 | [x] 361 | (cond (coll? x) (into [] x) 362 | (seq? x) (into [] x) 363 | :default [x])) 364 | 365 | 366 | (defn recursive-assembly-paths 367 | ([dir parent-name-vec] {:pre [(or (instance? File dir) (string? dir)) 368 | (vector? parent-name-vec)]} 369 | (let [dir (if (string? dir) (File. dir) dir) 370 | as-name #(str/join File/separator (conj parent-name-vec %)) 371 | entries (.listFiles ^File dir) 372 | is-dll? (fn [^File f] (and (.isFile f) 373 | (re-find #"\.([dD][lL][lL]|[eE][xX][eE])$" 374 | (.getName f)))) 375 | d-files (map (comp as-name #(.getName %)) (filter is-dll? entries)) 376 | subdirs (filter #(.isDirectory ^File %) entries)] 377 | (-> #(recursive-assembly-paths % (conj parent-name-vec (.getName %))) 378 | (mapcat subdirs) 379 | (concat d-files)))) 380 | ([dir] 381 | (recursive-assembly-paths dir []))) 382 | 383 | 384 | (defn filter-assembly-paths 385 | [[base regex]] 386 | (->> (recursive-assembly-paths base) 387 | (filter (fn [name] 388 | (let [r (or regex #".*")] 389 | (if (re-find r name) 390 | (do (verbose "Including assembly file" name) true) 391 | (verbose "NOT including assembly file" name))))) 392 | (map (partial str base File/separator)))) 393 | 394 | 395 | (defn spit-assembly-load-instruction 396 | [temp-file assembly-paths] 397 | (when (seq assembly-paths) 398 | (let [content (->> assembly-paths 399 | (map (comp (partial format "(assembly-load-from %s)") pr-str)) 400 | (str/join "\n"))] 401 | (spit temp-file content 402 | :append true)))) 403 | 404 | 405 | (defn spit-require-ns 406 | [temp-file nses] 407 | (when (seq nses) 408 | (spit temp-file (-> (partial format "\n(require '%s)") 409 | (map nses) 410 | str/join) 411 | :append true))) 412 | 413 | 414 | (defn verbose-init-with 415 | [temp-file] 416 | (verbose "Initializing with:" (slurp temp-file))) 417 | 418 | 419 | (defn unzip-file 420 | [zip-filename dest regex] {:pre [(string? zip-filename) 421 | (string? dest)]} 422 | (with-open [^ZipFile zip-file (ZipFile. zip-filename)] 423 | (let [^Enumeration zip-entries (.entries zip-file)] 424 | (while (.hasMoreElements zip-entries) 425 | (let [^ZipEntry entry (.nextElement zip-entries) 426 | ^String ename (.getName entry) 427 | ^String epath (str dest File/separator ename)] 428 | (when (re-find regex ename) 429 | (if (.isDirectory entry) 430 | (do (verbose "Creating directory:" ename) 431 | (mkdir-p epath)) 432 | (do (verbose "Extracting file:" ename) 433 | (mkdir-p (.getParentFile (File. epath))) 434 | (io/copy (.getInputStream zip-file entry) 435 | (io/file epath)))))))))) 436 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-clr/parent "0.0.0" 2 | :description "Housekeeping project for lein-clr" 3 | :url "https://github.com/kumarshantanu/lein-clr" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :min-lein-version "2.0.0" 7 | :plugins [[lein-sub "0.2.4"]] 8 | :sub ["lein-template" 9 | "plugin"] 10 | :eval-in :leiningen) 11 | -------------------------------------------------------------------------------- /sample.project.clj: -------------------------------------------------------------------------------- 1 | ;; This is an annotated example of the options that may be set in the 2 | ;; :clr map of a project.clj file. It is a fairly contrived example 3 | ;; in order to cover all options exhaustively. 4 | (defproject org.example/sample "0.1.0-SNAPSHOT" 5 | ;; ----- other entries omitted ----- 6 | ;; (required) Project level usage of the lein-clr plugin is recommended 7 | :plugins [[lein-clr "0.2.1"]] 8 | ;; (optional) regular attributes used by lein-clr 9 | :dependencies [] ; JAR files are decompressed and .clj files are put on load-path 10 | :source-paths ["src"] 11 | :resource-paths ["resources"] 12 | :test-paths ["test"] 13 | :aot [#"foo\.core"] 14 | :main foo.core 15 | :target-path "target" ; to infer CLR target-path when [:clr :target-path] is unspecified 16 | ;; Configuration for the :lein-clr plugin (required) 17 | :clr {;; (optional) command templates 18 | ;; Command templates are an easy way to reuse lengthy commands and command arguments. 19 | ;; You can use them wherever you need to provide command vectors, as in the examples 20 | ;; in :deps-cmds, :compile-cmd and :main-cmd below. Note that only keywords correspond 21 | ;; to command template keys. 22 | :cmd-templates {;; uses specified file at a location pointed to by env-var CLJCLR14_40 23 | ;; ?PATH instructs lein-clr to ignore the token if not found in PATH 24 | :clj-exe [[?PATH "mono"] [CLJCLR14_40 %1]] 25 | ;; uses specified file at location "target/clr/clj/Debug 4.0" 26 | :clj-dep [[?PATH "mono"] ["target/clr/clj/Debug 4.0" %1]] 27 | ;; ClojureCLR download URL 28 | :clj-url "http://sourceforge.net/projects/clojureclr/files/clojure-clr-1.4.1-Debug-4.0.zip/download" 29 | ;; ClojureCLR ZIP filename after download 30 | :clj-zip "clojure-clr-1.4.1-Debug-4.0.zip" 31 | ;; Fetch a file from remote-url (%2) into a local file (%1) using cURL 32 | :curl ["curl" "--insecure" "-f" "-L" "-o" %1 %2] 33 | ;; Fetch specific version (%2) of a dependency (%1) using NuGet 34 | :nuget-ver [[?PATH "mono"] [*PATH "nuget.exe"] "install" %1 "-Version" %2] 35 | ;; Fetch any/latest version of a dependency (%1) using NuGet 36 | :nuget-any [[?PATH "mono"] [*PATH "nuget.exe"] "install" %1] 37 | ;; Unzip a ZIP file (%2) into specified destination directory (%1) 38 | :unzip ["unzip" "-d" %1 %2] 39 | ;; Fetch a file from remote-url (%2) into a local file (%1) using wget 40 | :wget ["wget" "--no-check-certificate" "--no-clobber" "-O" %1 %2]} 41 | ;; (optional) comands to fetch dependencies, they are run in lib folder 42 | ;; You can use command templates to specify those, or use ordinary command vector. 43 | :deps-cmds [;; using curl to download ClojureCLR 1.4 44 | [:curl :clj-zip :clj-url] 45 | ;; using wget to download ClojureCLR 1.4 46 | [:wget :clj-zip :clj-url] 47 | ;; unzip the downloaded ClojureCLR ZIP file 48 | [:unzip "../clj" :clj-zip] 49 | ;; NuGet dependency by version 50 | [:nuget-ver "NHibernate" "3.3.2.4000"] 51 | ;; NuGet dependency (any/latest version) 52 | [:nuget-any "MySQL.Data"] 53 | ;; copy dependency files (for demonstration only, not recommended) 54 | ["cp" "-r" "C:\\alldeps\\foo" "."]] 55 | ;; (optional) for matching path of assembly dependencies 56 | ;; the deps-cmds may download many files, not all of which you may want to include 57 | ;; use regex to filter out unwanted files 58 | :assembly-deps-regex #".*[Nn]et(40|35).*" 59 | ;; (optional) the compile command - a command vector having executable and optional args 60 | ;; You can use command template or ordinary command vector to specify this. 61 | ;; For Mono, you may use ["mono" [*PATH "Clojure.Compile.exe"]] instead of the example below 62 | ;; A 2-element vector where the first element is a symbol beginning with an asterisk 63 | ;; is treated as search-path - first element (env val) is searched for second element. 64 | :compile-cmd ["Clojure.Compile.exe"] ; .NET default, Mono example below 65 | ;; (optional) the main command - a vector having executable and optional args 66 | ;; A symbol is looked up as environment variable, 67 | ;; vector elements are concatenated using path-separator 68 | :main-cmd ["mono" [CLJCLR14_PATH "Clojure.Main.exe"]] 69 | ;; (optional) path to third-party assemblies other than `:deps-cmds` 70 | ;; note that you can optionally provide a regex to match the path 71 | :assembly-paths [["ext/foo" #".*[Nn]et40.*"] 72 | "ext/baaz"] 73 | ;; (optional) path where build files are stored 74 | :target-path "path/to/clr-build/files"}) 75 | --------------------------------------------------------------------------------