Reach Ant's task and type ecosystem from Clojure.
This is not a build tool -- it's an interface that lets you call Ant tasks and types as if they were Clojure functions, with the results readable as Clojure data. Each call returns a tree of plain maps:
(copy :todir "out"
(fileset :dir "src" :includes "**/*.clj"))
;; => a element map -- nothing runs yet
Pass that tree to execute! (or ant) to invoke Ant on it. At
that point clj-ant assembles Ant's own AST (UnknownElement +
RuntimeConfigurable) directly -- the same tree Ant's XML parser
produces -- so property expansion (${foo}), refid, macrodef,
presetdef, if/unless attributes etc. all work for free,
because they are implemented inside Ant's runtime configuration
machinery, not its XML layer.
Reach Ant's task and type ecosystem from Clojure.
This is not a build tool -- it's an interface that lets you call
Ant tasks and types as if they were Clojure functions, with the
results readable as Clojure data. Each call returns a tree of
plain maps:
(copy :todir "out"
(fileset :dir "src" :includes "**/*.clj"))
;; => a element map -- nothing runs yet
Pass that tree to `execute!` (or `ant`) to invoke Ant on it. At
that point clj-ant assembles Ant's own AST (`UnknownElement` +
`RuntimeConfigurable`) directly -- the same tree Ant's XML parser
produces -- so property expansion (`${foo}`), `refid`, `macrodef`,
`presetdef`, `if`/`unless` attributes etc. all work for free,
because they are implemented inside Ant's runtime configuration
machinery, not its XML layer.If non-nil, execute! reuses this Project instead of
building a fresh one. Bind via with-project / with-session
for REPL-driven workflows where the per-call init adds up.
If non-nil, `execute!` reuses this Project instead of building a fresh one. Bind via `with-project` / `with-session` for REPL-driven workflows where the per-call init adds up.
(ant & args)Run a sequence of Ant tasks inline. Each top-level form is added as a
task of an implicit unnamed target. Options are forwarded to
execute! / make-project and may be supplied either as leading
keyword/value pairs or as one leading map.
Example:
(ant
{:basedir "out"}
(mkdir :dir "classes")
(copy :todir "classes"
(fileset :dir "src" :includes "**/*.clj")))
Note that the task functions like mkdir, copy, fileset live in
clj-ant.tasks (auto-generated). For ad-hoc/unknown elements, use
(element :my-tag ...) from this namespace.
Run a sequence of Ant tasks inline. Each top-level form is added as a
task of an implicit unnamed target. Options are forwarded to
`execute!` / `make-project` and may be supplied either as leading
keyword/value pairs or as one leading map.
Example:
(ant
{:basedir "out"}
(mkdir :dir "classes")
(copy :todir "classes"
(fileset :dir "src" :includes "**/*.clj")))
Note that the task functions like `mkdir`, `copy`, `fileset` live in
`clj-ant.tasks` (auto-generated). For ad-hoc/unknown elements, use
`(element :my-tag ...)` from this namespace.(as-child x)Coerce x to a child usable inside an element tree. Dispatches
through the ICoercible protocol -- extend that protocol for
your own types.
Built-in dispatches:
Element / JavaChild -> as is org.apache.tools.ant.types.ResourceCollection -> JavaChild (refid proxy at execute time) File / Resource -> single-element ResourceCollection Sequential of the above -> lazy ResourceCollection over the seq map (element-shaped) -> as is
You don't usually call this yourself -- element (and the
generated task wrappers) call it on every positional arg. So
this works out of the box:
(a/ant (t/copy :todir "out" my-fileset)) ; FileSet
(a/ant (t/copy :todir "out" (find-files))) ; lazy seq of File
(a/ant (t/copy :todir "out" (io/file "x"))) ; one File
Coerce `x` to a child usable inside an element tree. Dispatches
through the `ICoercible` protocol -- extend that protocol for
your own types.
Built-in dispatches:
Element / JavaChild -> as is
org.apache.tools.ant.types.ResourceCollection
-> JavaChild (refid proxy at execute time)
File / Resource -> single-element ResourceCollection
Sequential of the above -> lazy ResourceCollection over the seq
map (element-shaped) -> as is
You don't usually call this yourself -- `element` (and the
generated task wrappers) call it on every positional arg. So
this works out of the box:
(a/ant (t/copy :todir "out" my-fileset)) ; FileSet
(a/ant (t/copy :todir "out" (find-files))) ; lazy seq of File
(a/ant (t/copy :todir "out" (io/file "x"))) ; one File(cancel! run)Cancel a Run started via execute-async! -- sets the cancelled
flag and Thread.interrupts the build thread. IO tasks (scp,
get, sshexec) abort cleanly; CPU-bound tasks (large copies,
javac) often don't observe the interrupt and run to completion.
Either way, :cancelled? true is on the eventual result map.
Returns the Run.
Cancel a Run started via `execute-async!` -- sets the cancelled flag and `Thread.interrupt`s the build thread. IO tasks (scp, get, sshexec) abort cleanly; CPU-bound tasks (large copies, javac) often don't observe the interrupt and run to completion. Either way, `:cancelled? true` is on the eventual result map. Returns the Run.
(cancelled? run)True once cancel! has been called on this Run (regardless of
whether the build itself observed the interrupt).
True once `cancel!` has been called on this Run (regardless of whether the build itself observed the interrupt).
(close-session _session)Release a session. Currently the Project is just dropped from the dynamic binding; held resources clean up under GC. Kept for forward compatibility -- if we ever attach long-lived resources to a Session (caches, listeners) this is the cleanup hook.
Release a session. Currently the Project is just dropped from the dynamic binding; held resources clean up under GC. Kept for forward compatibility -- if we ever attach long-lived resources to a Session (caches, listeners) this is the cleanup hook.
(deftarget nm & body)def a target element bound to nm, with :name set from the symbol's
name. Body is the same shape as target minus the :name attribute.
(deftarget compile
:depends [:clean]
(mkdir :dir "classes")
(javac :srcdir "src" :destdir "classes"))
Then run with:
(a/ant :targets ["compile"] compile clean)
(a/ant compile clean) ; runs `compile` since it is first
def a target element bound to `nm`, with :name set from the symbol's
name. Body is the same shape as `target` minus the :name attribute.
(deftarget compile
:depends [:clean]
(mkdir :dir "classes")
(javac :srcdir "src" :destdir "classes"))
Then run with:
(a/ant :targets ["compile"] compile clean)
(a/ant compile clean) ; runs `compile` since it is first(deftask tag f)Register a Clojure fn as an Ant task named tag.
The fn is called on execute with one argument: a map containing
:project the org.apache.tools.ant.Project
:task-name the registered tag name (a String)
:text the element's text body, if any
<attr> one keyword entry per attribute; values are strings
with ${...} property references already expanded.
Side-effecting: the registration is global. Every Project clj-ant
builds via make-project after this call has the task installed.
Re-registering the same tag replaces the previous fn.
Example: (a/deftask :slack-notify (fn [{:keys [channel msg webhook]}] (slack/post webhook channel msg)))
(a/ant
(a/element :slack-notify :webhook url
:channel "#deploys"
:msg "shipped ${version}"))
Register a Clojure fn as an Ant task named `tag`.
The fn is called on execute with one argument: a map containing
:project the org.apache.tools.ant.Project
:task-name the registered tag name (a String)
:text the element's text body, if any
<attr> one keyword entry per attribute; values are strings
with `${...}` property references already expanded.
Side-effecting: the registration is global. Every Project clj-ant
builds via `make-project` after this call has the task installed.
Re-registering the same `tag` replaces the previous fn.
Example:
(a/deftask :slack-notify
(fn [{:keys [channel msg webhook]}]
(slack/post webhook channel msg)))
(a/ant
(a/element :slack-notify :webhook url
:channel "#deploys"
:msg "shipped ${version}"))(deftask? tag)Truthy if tag has been registered via deftask.
Truthy if `tag` has been registered via `deftask`.
(describe tag)Return a data description of a task or type by tag. Useful for building UIs, validators, or just satisfying curiosity at the REPL.
(describe :copy)
=> {:tag :copy
:class "org.apache.tools.ant.taskdefs.Copy"
:classes ["...Copy"]
:kind :task
:description "Copies a file or resource collection ..."
:manual-url "https://ant.apache.org/manual/Tasks/copy.html"
:attrs {"todir" {:type File
:description "The directory to copy to."
:required "..."}
"tofile" {:type File, ...}
...}
:nested {:fileset FileSet, ...}
:text? true|false}
Works for nested-only tags too (:attribute, :tokenfilter,
:replacestring, ...). For ambiguous tags (:attribute is
MacroDef$Attribute under <macrodef> and Manifest$Attribute under
<manifest>) the result merges every observed class:
:class -> the canonical class (first one seen by the generator) :classes -> every recorded class :attrs -> union across all classes :nested -> union across all classes
Return a data description of a task or type by tag. Useful for
building UIs, validators, or just satisfying curiosity at the REPL.
(describe :copy)
=> {:tag :copy
:class "org.apache.tools.ant.taskdefs.Copy"
:classes ["...Copy"]
:kind :task
:description "Copies a file or resource collection ..."
:manual-url "https://ant.apache.org/manual/Tasks/copy.html"
:attrs {"todir" {:type File
:description "The directory to copy to."
:required "..."}
"tofile" {:type File, ...}
...}
:nested {:fileset FileSet, ...}
:text? true|false}
Works for nested-only tags too (`:attribute`, `:tokenfilter`,
`:replacestring`, ...). For ambiguous tags (`:attribute` is
MacroDef$Attribute under <macrodef> and Manifest$Attribute under
<manifest>) the result merges every observed class:
:class -> the canonical class (first one seen by the generator)
:classes -> every recorded class
:attrs -> union across all classes
:nested -> union across all classes(element tag-kw & args)Build a clj-ant element for tag tag-kw. The remaining args follow
hiccup-ish positional rules:
Returned value is an Element record — a plain map you can inspect,
walk, diff, transform with update, store in an atom, etc.
Build a clj-ant element for tag `tag-kw`. The remaining args follow hiccup-ish positional rules: * keyword/value pairs are attributes * strings concatenate into the element's text body * maps or other elements become children * sequential collections of elements/maps splice in as children * sequential collections of files/resources/paths become one resource child Returned value is an `Element` record — a plain map you can inspect, walk, diff, transform with `update`, store in an atom, etc.
(element? x)Truthy if x is a clj-ant element.
Truthy if `x` is a clj-ant element.
(elements tree)(elements tree pred)Lazy depth-first seq of every element in tree.
Walks Elements, JavaChild wrappers, and node-shaped maps -- the
same vocabulary as-child produces. Non-node payloads (raw
vectors of File paths, etc.) are leaves and skipped over.
With pred, only nodes matching pred:
(elements tree #(= :scp (:tag %)))
(elements tree #(and (= :javac (:tag %))
(= "false" (-> % :attrs :debug))))
Lazy depth-first seq of every element in `tree`.
Walks Elements, JavaChild wrappers, and node-shaped maps -- the
same vocabulary `as-child` produces. Non-node payloads (raw
vectors of File paths, etc.) are leaves and skipped over.
With `pred`, only nodes matching pred:
(elements tree #(= :scp (:tag %)))
(elements tree #(and (= :javac (:tag %))
(= "false" (-> % :attrs :debug))))(execute! elements & {:as opts})Execute one or more elements against a fresh (or supplied) project.
Top-level forms become tasks of an implicit unnamed target which is then executed. Returns a map containing the project, the target, the list of UnknownElements that ran, plus any captured events.
Options:
:project An existing org.apache.tools.ant.Project. If absent, a fresh one is built from the same options. :capture? If true, attach a recording BuildListener and return the captured events under :events. Default false.
Plus any options accepted by make-project.
Execute one or more elements against a fresh (or supplied) project.
Top-level forms become tasks of an implicit unnamed target which is
then executed. Returns a map containing the project, the target, the
list of UnknownElements that ran, plus any captured events.
Options:
:project An existing org.apache.tools.ant.Project. If absent, a
fresh one is built from the same options.
:capture? If true, attach a recording BuildListener and return
the captured events under :events. Default false.
Plus any options accepted by `make-project`.(execute-async! nodes & {:as opts})Run nodes on a daemon background thread. Returns a Run handle
that behaves like a promise/future:
@run ; blocks for the result map
(deref run ms timeout) ; bounded wait, returns timeout-val
(realized? run) ; true once finished/cancelled/failed
(cancel! run) ; interrupts the build thread
All the options accepted by execute! work here too -- including
:on-event for streaming events while the build runs.
Implementation note: just clojure.core/promise + a daemon
Thread. No Executors, no Future.cancel to interact with.
The thread is daemon so a stray async run does not pin the JVM
open after main returns -- a real bug in scripts, hard to
spot otherwise.
Run `nodes` on a daemon background thread. Returns a Run handle
that behaves like a `promise`/`future`:
@run ; blocks for the result map
(deref run ms timeout) ; bounded wait, returns timeout-val
(realized? run) ; true once finished/cancelled/failed
(cancel! run) ; interrupts the build thread
All the options accepted by `execute!` work here too -- including
`:on-event` for streaming events while the build runs.
Implementation note: just `clojure.core/promise` + a daemon
`Thread`. No `Executors`, no `Future.cancel` to interact with.
The thread is daemon so a stray async run does not pin the JVM
open after `main` returns -- a real bug in scripts, hard to
spot otherwise.(files element & {:as opts})Lazy seq of java.io.File from a resource-collection element. Resources
that don't resolve to a filesystem path (HTTP, zip entry, …) are
passed through clojure.java.io/file on their name.
Lazy seq of `java.io.File` from a resource-collection element. Resources that don't resolve to a filesystem path (HTTP, zip entry, …) are passed through `clojure.java.io/file` on their name.
(from-xml src)Parse an Ant build file into a clj-ant element tree.
src may be:
java.io.File, InputStream, or Reader<)Returns the root :project element. Pass it to execute! or ant
to run the file -- the runner unwraps it transparently and lifts
the project's name/basedir/default attributes into execute options.
(a/ant (a/from-xml "build.xml"))
(a/ant :targets ["clean"] (a/from-xml "build.xml"))
Relative paths inside the build file resolve from the source
file's directory, matching Ant's own semantics. (For string and
stream sources the source dir is unknown, so paths fall back to
the process cwd.) The captured source dir is exposed under
:clj-ant/source-dir on the returned element's attrs map, so
static analysers can tell on-disk roots apart from
string-literal ones.
For static analysis just walk the returned tree like any other element.
Parse an Ant build file into a clj-ant element tree.
`src` may be:
* a path to a file on disk
* a `java.io.File`, `InputStream`, or `Reader`
* an XML string (detected by leading `<`)
Returns the root `:project` element. Pass it to `execute!` or `ant`
to run the file -- the runner unwraps it transparently and lifts
the project's name/basedir/default attributes into execute options.
(a/ant (a/from-xml "build.xml"))
(a/ant :targets ["clean"] (a/from-xml "build.xml"))
Relative paths inside the build file resolve from the **source
file's directory**, matching Ant's own semantics. (For string and
stream sources the source dir is unknown, so paths fall back to
the process cwd.) The captured source dir is exposed under
`:clj-ant/source-dir` on the returned element's attrs map, so
static analysers can tell on-disk roots apart from
string-literal ones.
For static analysis just walk the returned tree like any other
element.Coerce a value to a child usable inside an element tree.
The default extensions cover everything the runner already handles: Element / JavaChild records (passed through), Ant ResourceCollection / Resource / File (wrapped as a refid'd proxy), node-shaped maps (passed through), and Sequential of any of the above (wrapped as a lazy ResourceCollection).
To plug in your own type without forking, extend the protocol:
(extend-protocol clj-ant.core/ICoercible
my.lib.SomeFileBag
(-as-child [bag]
(clj-ant.core/->JavaChild
(.toAntResourceCollection bag) :resources)))
After that, instances flow through as-child (and therefore
any task wrapper) like any other built-in shape.
Coerce a value to a child usable inside an element tree.
The default extensions cover everything the runner already
handles: Element / JavaChild records (passed through), Ant
ResourceCollection / Resource / File (wrapped as a refid'd
proxy), node-shaped maps (passed through), and Sequential of
any of the above (wrapped as a lazy ResourceCollection).
To plug in your own type without forking, extend the protocol:
(extend-protocol clj-ant.core/ICoercible
my.lib.SomeFileBag
(-as-child [bag]
(clj-ant.core/->JavaChild
(.toAntResourceCollection bag) :resources)))
After that, instances flow through `as-child` (and therefore
any task wrapper) like any other built-in shape.(-as-child x)Convert x to an Element / JavaChild / node-shaped map.
Implementations should return a value the runner can drop
into the children list.
Convert `x` to an Element / JavaChild / node-shaped map. Implementations should return a value the runner can drop into the children list.
(java-child? x)Truthy if x is a child-wrapped real Ant object.
Truthy if `x` is a `child`-wrapped real Ant object.
(lazy-resources files)(lazy-resources files
{:keys [size filesystem-only?] :or {filesystem-only? true}})Wrap a (possibly lazy/infinite) seq of java.io.File / Resource
/ path-string as a real Ant ResourceCollection. Iteration is on
demand: the resulting object pulls one FileResource at a time
from the seq, so a 10M-entry input never materialises into 10M
live objects up front.
Returns a value you can drop into any task that accepts a resource collection (copy, jar, zip, tar, …).
(a/ant
(t/copy :todir "out"
(a/lazy-resources (find-millions-of-files)
{:size 10000000})))
Options:
:size size hint, returned by ResourceCollection.size().
Default: (count files) -- forces full
realisation. Pass an explicit size to skip
the count when you already know it.
:filesystem-only? default true. Set false for HTTP / zip /
tar / non-filesystem resources.
Wrap a (possibly lazy/infinite) seq of `java.io.File` / `Resource`
/ path-string as a real Ant `ResourceCollection`. Iteration is on
demand: the resulting object pulls one `FileResource` at a time
from the seq, so a 10M-entry input never materialises into 10M
live objects up front.
Returns a value you can drop into any task that accepts a resource
collection (copy, jar, zip, tar, …).
(a/ant
(t/copy :todir "out"
(a/lazy-resources (find-millions-of-files)
{:size 10000000})))
Options:
:size size hint, returned by ResourceCollection.size().
Default: `(count files)` -- forces full
realisation. Pass an explicit size to skip
the count when you already know it.
:filesystem-only? default true. Set false for HTTP / zip /
tar / non-filesystem resources.(lint elements & {:as opts})Validate an element tree without running Ant. Returns a vector of issue
maps from clj-ant.spec/validate-tree; empty means no issues were found.
Unlike execute! :validate? true, lint defaults to :closed? true so it
catches misspelled attributes and can include :suggestions:
(lint (element :copy :tdoir "out"))
=> [{:tag :copy
:path []
:errors {:tdoir ["disallowed key"]}
:suggestions {:tdoir [:todir]}}]
Validate an element tree without running Ant. Returns a vector of issue
maps from `clj-ant.spec/validate-tree`; empty means no issues were found.
Unlike `execute! :validate? true`, lint defaults to `:closed? true` so it
catches misspelled attributes and can include `:suggestions`:
(lint (element :copy :tdoir "out"))
=> [{:tag :copy
:path []
:errors {:tdoir ["disallowed key"]}
:suggestions {:tdoir [:todir]}}](make-project {:keys [basedir name listeners props]
:or {basedir "." name "clj-ant"}
:as opts})Build a fresh org.apache.tools.ant.Project, attach a logger, and
call init so all built-in tasks/types are registered.
Options:
:basedir String or File. Default: current working directory.
:name Project name string. Default: "clj-ant".
:level :error | :warn | :info | :verbose | :debug. Default :info.
:out, :err PrintStream for the default logger.
:emacs? Strip the [taskname] adornments. Default false.
:listeners Coll of BuildListener instances to attach.
:props Map of user properties to seed.
Build a fresh `org.apache.tools.ant.Project`, attach a logger, and call `init` so all built-in tasks/types are registered. Options: :basedir String or File. Default: current working directory. :name Project name string. Default: "clj-ant". :level :error | :warn | :info | :verbose | :debug. Default :info. :out, :err PrintStream for the default logger. :emacs? Strip the [taskname] adornments. Default false. :listeners Coll of `BuildListener` instances to attach. :props Map of user properties to seed.
(plan element)(plan element depth)Pretty-print a element tree to out. Useful for sanity-checking a
tree before running it. Returns the element unchanged so it can be
threaded into ant / execute!.
Pretty-print a element tree to *out*. Useful for sanity-checking a tree before running it. Returns the element unchanged so it can be threaded into `ant` / `execute!`.
(prepare elements & {:as opts})Coerce, validate, and freeze an element tree. Returns a Plan
ready to be run against a session many times. Combine with
with-session for the lowest steady-state cost in tight REPL
loops:
(a/with-session [s {:level :info}]
(let [p (a/prepare elements :validate? true)]
(dotimes [_ 100] (a/run p :session s))))
The big perf win is session reuse; prepare is the smaller
additional optimisation on top, removing per-call coercion +
validation walks.
Coerce, validate, and freeze an element tree. Returns a Plan
ready to be `run` against a session many times. Combine with
`with-session` for the lowest steady-state cost in tight REPL
loops:
(a/with-session [s {:level :info}]
(let [p (a/prepare elements :validate? true)]
(dotimes [_ 100] (a/run p :session s))))
The big perf win is session reuse; `prepare` is the smaller
additional optimisation on top, removing per-call coercion +
validation walks.(realize element & {:as opts})Materialise a element into a live Ant object (Task or DataType) without
executing it. Property references are expanded; refids resolve;
nested elements are configured. Returns the underlying Ant object
(e.g. org.apache.tools.ant.types.FileSet).
Options:
:project an existing Project, or nil to create one. Other keys
are forwarded to make-project.
:fast? skip the UnknownElement layer when the element is a
simple top-level type with no children / ${...} refs
/ refid. Default true. Saves ~25-30 ms per call on
warm projects -- meaningful for (a/files ...) in
tight loops.
Materialise a element into a live Ant object (Task or DataType) without
executing it. Property references are expanded; `refid`s resolve;
nested elements are configured. Returns the underlying Ant object
(e.g. `org.apache.tools.ant.types.FileSet`).
Options:
:project an existing Project, or nil to create one. Other keys
are forwarded to `make-project`.
:fast? skip the UnknownElement layer when the element is a
simple top-level type with no children / `${...}` refs
/ refid. Default true. Saves ~25-30 ms per call on
warm projects -- meaningful for `(a/files ...)` in
tight loops.(resources element & {:as opts})Lazily realise a resource-collection element and return a seq of
org.apache.tools.ant.types.Resource. Works on any element whose backing
Ant type implements ResourceCollection (fileset, filelist, path,
files, dirset, restrict, intersect, …).
Lazily realise a resource-collection element and return a seq of `org.apache.tools.ant.types.Resource`. Works on any element whose backing Ant type implements `ResourceCollection` (fileset, filelist, path, files, dirset, restrict, intersect, …).
(run plan & {:as opts})Execute a previously-prepared Plan. Re-runs are cheap when paired
with with-session.
Execute a previously-`prepare`d Plan. Re-runs are cheap when paired with `with-session`.
(session)(session opts)Create a long-lived session backed by a single Ant Project. Reuse across many execute!/ant calls to amortise the per-call init + logger setup + taskdef registration cost.
(def s (a/session {:level :info}))
(a/ant :session s (t/echo :message "first"))
(a/ant :session s (t/echo :message "second"))
(a/close-session s)
Or, with automatic cleanup:
(a/with-session [s {:level :info}]
(a/ant (t/echo :message "first"))
(a/ant (t/echo :message "second")))
Properties carry over between calls in the same session -- that's
Ant's normal Project semantics. Pass :project explicitly to a
single call to opt out for that call.
opts are the same options accepted by make-project.
Create a long-lived session backed by a single Ant Project.
Reuse across many execute!/ant calls to amortise the per-call
init + logger setup + taskdef registration cost.
(def s (a/session {:level :info}))
(a/ant :session s (t/echo :message "first"))
(a/ant :session s (t/echo :message "second"))
(a/close-session s)
Or, with automatic cleanup:
(a/with-session [s {:level :info}]
(a/ant (t/echo :message "first"))
(a/ant (t/echo :message "second")))
Properties carry over between calls in the same session -- that's
Ant's normal Project semantics. Pass `:project` explicitly to a
single call to opt out for that call.
`opts` are the same options accepted by `make-project`.(target & args)Build a target element. The name attribute is required.
(target :name "compile" :depends [:clean] :description "build the jar" (mkdir :dir "classes") (javac :srcdir "src" :destdir "classes"))
Build a target element. The name attribute is required.
(target :name "compile"
:depends [:clean]
:description "build the jar"
(mkdir :dir "classes")
(javac :srcdir "src" :destdir "classes"))(task f)(task tag f)Inline a Clojure thunk as an Ant task. Returns an element you can drop into any element tree. The fn runs in target order with full event-stream participation -- you'll see :task-started / :task-finished for the chosen tag.
(a/ant
(t/echo :message "before")
(a/task :compile #(b/javac {:src-dirs ["src"]
:class-dir "out" :basis @basis}))
(a/task :jar #(b/jar {:class-dir "out"
:jar-file "target/app.jar"}))
(t/scp :file "target/app.jar" :todir "deploy@host:/srv/"))
Use this when you need arbitrary Clojure code between Ant tasks -- tools.build operations, slack notifications, query an API, mutate an atom -- and the args are richer than Ant's string-attribute model can carry.
Unlike deftask, the fn is NOT added to a global registry.
Instead, it's attached to the element as :clj-ant/inline-fn
metadata; the runner registers it on the project just before
execution and removes it in finally. So a 1000-call REPL
session leaves no entries behind. Use deftask when you want a
reusable named task across many builds.
Inline a Clojure thunk as an Ant task. Returns an element you can
drop into any element tree. The fn runs in target order with full
event-stream participation -- you'll see :task-started /
:task-finished for the chosen tag.
(a/ant
(t/echo :message "before")
(a/task :compile #(b/javac {:src-dirs ["src"]
:class-dir "out" :basis @basis}))
(a/task :jar #(b/jar {:class-dir "out"
:jar-file "target/app.jar"}))
(t/scp :file "target/app.jar" :todir "deploy@host:/srv/"))
Use this when you need arbitrary Clojure code between Ant tasks
-- tools.build operations, slack notifications, query an API,
mutate an atom -- and the args are richer than Ant's
string-attribute model can carry.
Unlike `deftask`, the fn is NOT added to a global registry.
Instead, it's attached to the element as `:clj-ant/inline-fn`
metadata; the runner registers it on the project just before
execution and removes it in `finally`. So a 1000-call REPL
session leaves no entries behind. Use `deftask` when you want a
reusable named task across many builds.(to-xml tree)Render a clj-ant element tree to compact Ant XML.
This is the inverse of from-xml for normal data trees. It renders
Element records and element-shaped maps, escapes XML text and
attribute values, and omits clj-ant's internal namespaced attrs such as
:clj-ant/source-dir.
JVM-backed children (JavaChild, raw FileSets, lazy resource seqs)
cannot be represented faithfully as XML and will throw.
(to-xml (element :echo :message "hi"))
;; returns <echo message="hi"/>
Render a clj-ant element tree to compact Ant XML.
This is the inverse of `from-xml` for normal data trees. It renders
`Element` records and element-shaped maps, escapes XML text and
attribute values, and omits clj-ant's internal namespaced attrs such as
`:clj-ant/source-dir`.
JVM-backed children (`JavaChild`, raw `FileSet`s, lazy resource seqs)
cannot be represented faithfully as XML and will throw.
(to-xml (element :echo :message "hi"))
;; returns <echo message="hi"/>(transform tree f)Walk tree depth-first, applying f to each node. f must
return a node (or nil to drop it). Children are transformed
before parents see them, so f always observes already-rewritten
descendants.
Tolerates the full child vocabulary -- Element records, JavaChild wrappers, node-shaped maps, and non-node leaves (which pass through unchanged).
;; lowercase every :todir attribute everywhere in the tree
(transform tree
(fn [n]
(cond-> n
(-> n :attrs :todir)
(update-in [:attrs :todir]
clojure.string/lower-case))))
Walk `tree` depth-first, applying `f` to each node. `f` must
return a node (or nil to drop it). Children are transformed
before parents see them, so `f` always observes already-rewritten
descendants.
Tolerates the full child vocabulary -- Element records, JavaChild
wrappers, node-shaped maps, and non-node leaves (which pass
through unchanged).
;; lowercase every :todir attribute everywhere in the tree
(transform tree
(fn [n]
(cond-> n
(-> n :attrs :todir)
(update-in [:attrs :todir]
clojure.string/lower-case))))(watch nodes
&
{:keys [paths poll-ms on-rebuild]
:or {poll-ms 500
on-rebuild (fn* [p1__2178#]
(when-some [e (:error p1__2178#)]
(println "watch: error:"
(or (when (instance? Throwable
e)
(.getMessage e))
e))))}
:as opts})Run nodes once, then re-run whenever any file under :paths
changes (added / modified / removed). Returns a 0-arg stop! fn.
(def stop (a/watch [(t/javac :srcdir "src" :destdir "out")]
:paths ["src"]
:poll-ms 300))
;; ...edit, save, see rebuild on stdout...
(stop)
Options: :paths coll of dir paths to watch (required) :poll-ms polling interval, default 500 :on-rebuild fn called with the (cleaned) result map after each rebuild. Defaults to printing :error if any. :on-event forwarded to execute! for live event streaming. :session reuse a session across rebuilds (recommended -- amortises Project init across every rerun).
In-flight builds aren't cancelled when a new change arrives;
the watcher waits for the current build to finish before starting
the next. (Cancellation mid-build is supported via execute-async!
cancel!; combining the two is left to the caller for now.)Run `nodes` once, then re-run whenever any file under `:paths`
changes (added / modified / removed). Returns a 0-arg `stop!` fn.
(def stop (a/watch [(t/javac :srcdir "src" :destdir "out")]
:paths ["src"]
:poll-ms 300))
;; ...edit, save, see rebuild on stdout...
(stop)
Options:
:paths coll of dir paths to watch (required)
:poll-ms polling interval, default 500
:on-rebuild fn called with the (cleaned) result map after
each rebuild. Defaults to printing :error if any.
:on-event forwarded to execute! for live event streaming.
:session reuse a session across rebuilds (recommended --
amortises Project init across every rerun).
In-flight builds aren't cancelled when a new change arrives;
the watcher waits for the current build to finish before starting
the next. (Cancellation mid-build is supported via `execute-async!`
+ `cancel!`; combining the two is left to the caller for now.)(with-project project & body)Run body with project bound as the default Project for every
enclosed execute! / ant call.
(let [p (a/make-project {:level :info})]
(a/with-project p
(a/ant (t/echo :message "first"))
(a/ant (t/echo :message "second")))) ; same JVM Project
Properties set in earlier calls remain visible in later ones --
that's Ant's normal Project semantics. Pass an explicit
:project option to override for a single call.
Prefer with-session for new code; this stays for existing users
who hold a raw Project.
Run body with `project` bound as the default Project for every
enclosed `execute!` / `ant` call.
(let [p (a/make-project {:level :info})]
(a/with-project p
(a/ant (t/echo :message "first"))
(a/ant (t/echo :message "second")))) ; same JVM Project
Properties set in earlier calls remain visible in later ones --
that's Ant's normal Project semantics. Pass an explicit
`:project` option to override for a single call.
Prefer `with-session` for new code; this stays for existing users
who hold a raw Project.(with-session [sym opts] & body)Bind a fresh session for the duration of body. The session is
closed automatically on exit. opts are the options accepted by
make-project.
(a/with-session [s {:basedir "out" :level :info}]
(a/ant (t/property :name "v" :value "1"))
(a/ant (t/echo :message "v=${v}"))) ; v carries over
Bind a fresh session for the duration of body. The session is
closed automatically on exit. `opts` are the options accepted by
`make-project`.
(a/with-session [s {:basedir "out" :level :info}]
(a/ant (t/property :name "v" :value "1"))
(a/ant (t/echo :message "v=${v}"))) ; v carries overcljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |