Skip to content

Commit

Permalink
Add a "cljsbuild clean" task, and hook into "clean".
Browse files Browse the repository at this point in the history
Closes #12.
  • Loading branch information
emezeske committed Jan 19, 2012
1 parent 8c32877 commit d40c3a9
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 68 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ of your `project.clj` file. A simple project might look like this:
:pretty-print true}})
```

If you'd like your ClojureScript to be compiled whenever you run `lein compile`,
you can also add the following entry to your defproject config:
If you'd like your ClojureScript to be compiled when you run `lein compile`, and
deleted when you run `lein clean`, you can also add the following entry to your
defproject config:

```clojure
:hooks [leiningen.cljsbuild]
Expand All @@ -76,6 +77,11 @@ avoids the time-consuming JVM startup for each build:

$ lein cljsbuild auto

To delete all of the JavaScript and ClojureScript files that lein-cljsbuild
automatically generated during compilation, run:

$ lein cljsbuild clean

## Sharing Code Between Clojure and ClojureScript

Sharing code with lein-cljsbuild is accomplished via the configuration
Expand Down
1 change: 1 addition & 0 deletions example-projects/advanced/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[hiccup "0.3.7"]]
:dev-dependencies [[lein-cljsbuild "0.0.7"]
[lein-ring "0.5.0"]]
:hooks [leiningen.cljsbuild]
:cljsbuild {:source-path "src-cljs"
:crossovers [example.crossover]
:compiler {:output-to "resources/public/js/main.js"
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
; TODO: These dependencies are also maintained in leiningen.cljsbuild.
; Figure out some way to DRY them.
:dependencies [[org.clojure/clojure "1.3.0"]
[fs "1.1.0"]
[fs "1.1.2"]
[emezeske/clojurescript "0.0.4+f4c0de502c"]
[clj-stacktrace "0.2.4"]]
:eval-in-leiningen true)
75 changes: 44 additions & 31 deletions src/cljsbuild/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
[cljs.closure :only [build]])
(:require
[clojure.string :as string]
[fs :as fs]))
[fs.core :as fs]))

(defn- filter-cljs [files types]
(let [ext #(last (string/split % #"\."))]
(filter #(types (ext %)) files)))

(defn- join-paths [& paths]
(apply str (interpose "/" paths)))

(defn- find-dir-cljs [root files types]
(for [cljs (filter-cljs files types)] (fs/join root cljs)))
(for [cljs (filter-cljs files types)]
(join-paths root cljs)))

(defn- find-cljs [dir types]
(let [iter (fs/iterdir dir)]
(let [iter (fs/iterate-dir dir)]
(mapcat
(fn [[root _ files]]
(find-dir-cljs root files types))
Expand All @@ -28,11 +32,11 @@

(defn- compile-cljs [cljs-path compiler-options]
(let [output-file (:output-to compiler-options)
output-dir (fs/dirname output-file)]
output-dir (fs/parent output-file)]
(print (str "Compiling " output-file " from " cljs-path "..."))
(flush)
(when output-dir
(fs/mkdirs output-dir ))
(fs/mkdirs output-dir))
(let [started-at (. System (nanoTime))]
(try
(build cljs-path compiler-options)
Expand All @@ -41,6 +45,8 @@
(println " Failed!")
(pst+ e))))))

; TODO: Hmm, now that macro files are in the CLASSPATH, perhaps
; they should not be copied over the the cljs dir at all?
(defn- is-macro-file? [file]
(not (neg? (.indexOf (slurp file) ";*CLJSBUILD-MACRO-FILE*;"))))

Expand All @@ -57,10 +63,10 @@

(defn- crossover-to [cljs-path [from-parent from-resource]]
(let [subpath (string/replace-first
(fs/abspath (.getPath from-resource))
(fs/abspath from-parent) "")
to-file (fs/normpath
(fs/join (fs/abspath cljs-path) subpath))]
(fs/absolute-path (.getPath from-resource))
(fs/absolute-path from-parent) "")
to-file (fs/normalized-path
(join-paths (fs/absolute-path cljs-path) subpath))]
(if (is-macro-file? from-resource)
to-file
(string/replace to-file #"\.clj$" ".cljs"))))
Expand All @@ -70,7 +76,7 @@

(defn- ns-to-path [ns]
(let [underscored (string/replace (str ns) #"-" "_")]
(apply fs/join
(apply join-paths
(string/split underscored #"\."))))

(defn- fail [& args]
Expand All @@ -83,7 +89,7 @@
; by file.
(if (= (.getProtocol dir) "file")
(let [path (.getPath dir)
dirs (map #(as-url (str "file://" %)) (find-cljs path #{"clj"}))]
dirs (map #(as-url (str "file:" %)) (find-cljs path #{"clj"}))]
dirs)
[dir])))

Expand All @@ -93,8 +99,9 @@
(subs uri-path 0 (- (.length uri-path) n)))
nil))

(defn- find-crossover-resources [ns-path]
(let [as-dir (resource ns-path)
(defn- find-crossover-resources [crossover]
(let [ns-path (ns-to-path crossover)
as-dir (resource ns-path)
dir-parent (truncate-uri-path as-dir (.length ns-path))
recurse-dirs (recurse-resource-dir as-dir)
ns-file-path (str ns-path ".clj")
Expand All @@ -114,34 +121,30 @@
; We can't determine the mtime for jar resources; they'll just
; be copied once and that's it.
(= "file" (.getProtocol from-resource))
(> (fs/mtime (.getPath from-resource)) (fs/mtime to-file))))))
(> (fs/mod-time (.getPath from-resource)) (fs/mod-time to-file))))))

(defn- copy-crossovers [cljs-path crossovers]
(dofor [crossover crossovers]
(do
(when (map? crossover)
(fail "Sorry, crossovers now need to be specified by namespace rather than the old :from-dir/:to-dir map."))
(let [ns-path (ns-to-path crossover)
from-resources (find-crossover-resources ns-path)]
(when (empty? from-resources)
(fail "Unable to find crossover: " crossover))
(let [to-files (map (partial crossover-to cljs-path) from-resources)]
(doseq [dir (distinct (map fs/dirname to-files))]
(fs/mkdirs dir))
(dofor [[[_ from-resource] to-file] (zipmap from-resources to-files)]
(when (crossover-needs-update? from-resource to-file)
(spit to-file (filtered-crossover-file from-resource))
:updated)))))))
(let [from-resources (find-crossover-resources crossover)]
(when (empty? from-resources)
(fail "Unable to find crossover: " crossover))
(let [to-files (map (partial crossover-to cljs-path) from-resources)]
(doseq [dir (distinct (map fs/parent to-files))]
(fs/mkdirs dir))
(dofor [[[_ from-resource] to-file] (zipmap from-resources to-files)]
(when (crossover-needs-update? from-resource to-file)
(spit to-file (filtered-crossover-file from-resource))
:updated))))))

(defn run-compiler [cljs-path crossovers compiler-options watch?]
(println "Compiler started.")
(println "Compiling ClojureScript.")
(loop [last-input-mtimes {}]
(let [output-file (:output-to compiler-options)
output-mtime (if (fs/exists? output-file) (fs/mtime output-file) 0)
output-mtime (if (fs/exists? output-file) (fs/mod-time output-file) 0)
; Need to return *.clj as well as *.cljs because ClojureScript
; macros are written in Clojure.
input-files (find-cljs cljs-path #{"clj" "cljs"})
input-mtimes (map fs/mtime input-files)
input-mtimes (map fs/mod-time input-files)
crossover-updated? (some #{:updated}
(flatten
(copy-crossovers cljs-path crossovers)))]
Expand All @@ -154,3 +157,13 @@
(when watch?
(Thread/sleep 100)
(recur input-mtimes)))))

(defn cleanup-files [cljs-path crossovers compiler-options]
(println "Deleting generated files.")
(fs/delete (:output-to compiler-options))
(fs/delete-dir (:output-dir compiler-options))
(doseq [crossover crossovers]
(let [from-resources (find-crossover-resources crossover)
to-files (map (partial crossover-to cljs-path) from-resources)]
(doseq [file to-files]
(fs/delete file)))))
93 changes: 59 additions & 34 deletions src/leiningen/cljsbuild.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
"Compile ClojureScript source into a JavaScript file."
(:require
[robert.hooke :as hooke]
[leiningen.compile :as lcompile]))
[leiningen.compile :as lcompile]
[leiningen.clean :as lclean]))

; TODO: These are really the same as the :dependencies for the
; lein-cljsbuild project itself (e.g. in the toplevel project
; file). I haven't yet figured out a clean way to DRY them.
(def cljsbuild-dependencies
'[[org.clojure/clojure "1.3.0"]
[fs "1.1.0"]
[fs "1.1.2"]
[emezeske/clojurescript "0.0.4+f4c0de502c"]
[clj-stacktrace "0.2.4"]])

Expand All @@ -30,6 +31,8 @@
:optimizations [:compiler :optimizations]
:pretty-print [:compiler :pretty-print]})

(def exit-success 0)

(def exit-failure 1)

(defn- printerr [& args]
Expand All @@ -40,7 +43,7 @@
(apply printerr "WARNING:" args))

(defn- usage []
(printerr "Usage: lein cljsbuild [once|auto]"))
(printerr "Usage: lein cljsbuild [once|auto|clean]"))

(declare deep-merge-item)

Expand All @@ -62,43 +65,65 @@
(assoc-in {} dest value)))))
(keys relocations)))

(defn- run-local-project [project options form]
(lcompile/eval-in-project
{:local-repo-classpath true
:source-path (:source-path project)
:extra-classpath-dirs (conj
(:extra-classpath-dirs project)
(:source-path options))
:dependencies cljsbuild-dependencies}
form
nil
nil
'(require 'cljsbuild.core))
exit-success)

(defn- run-compiler [project options watch?]
(run-local-project project options
`(cljsbuild.core/run-compiler
~(:source-path options)
'~(:crossovers options)
~(:compiler options)
~watch?)))

(defn- cleanup-files [project options]
(run-local-project project options
`(cljsbuild.core/cleanup-files
~(:source-path options)
'~(:crossovers options)
~(:compiler options))))

(defn cljsbuild
; TODO: Add a decent docstring.
([project]
(usage)
exit-failure)
([project mode]
(let [cljsbuild (:cljsbuild project)
watch? (case mode "once" false "auto" true nil)]
(when (nil? cljsbuild)
(let [orig-options (:cljsbuild project)]
(when (nil? orig-options)
(warn "no :cljsbuild entry found in project definition."))
(if (nil? watch?)
(do
(usage)
exit-failure)
(let [compat-cljsbuild (backwards-compat cljsbuild)
options (deep-merge default-options compat-cljsbuild)]
(when (not= cljsbuild compat-cljsbuild)
(warn (str
"your deprecated :cljsbuild config was interpreted as:\n"
compat-cljsbuild)))
(lcompile/eval-in-project
{:local-repo-classpath true
:source-path (:source-path project)
:extra-classpath-dirs (conj
(:extra-classpath-dirs project)
(:source-path options))
:dependencies cljsbuild-dependencies}
`(cljsbuild.core/run-compiler
~(:source-path options)
'~(:crossovers options)
~(:compiler options)
~watch?)
nil
nil
'(require 'cljsbuild.core)))))))

(defn cljsbuild-hook [task & args]
(let [compat-options (backwards-compat orig-options)
options (deep-merge default-options compat-options)]
(when (not= orig-options compat-options)
(warn (str
"your deprecated :cljsbuild config was interpreted as:\n"
compat-options)))
(case mode
"once" (run-compiler project options false)
"auto" (run-compiler project options true)
"clean" (cleanup-files project options)
(do
(usage)
exit-failure))))))

(defn compile-hook [task & args]
(cljsbuild (first args) "once")
(apply task args))

(hooke/add-hook #'lcompile/compile cljsbuild-hook)
(defn clean-hook [task & args]
(cljsbuild (first args) "clean")
(apply task args))

(hooke/add-hook #'lcompile/compile compile-hook)
(hooke/add-hook #'lclean/clean clean-hook)

0 comments on commit d40c3a9

Please sign in to comment.