Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

little generalization to be able to use repositories with username/pwd, ... #6

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ pom.xml.asc
/boot
/build.clj
/docs
*.iml
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Stories in Ready](https://badge.waffle.io/tailrecursion/boot.core.png?label=ready&title=Ready)](https://waffle.io/tailrecursion/boot.core)
# boot.core

Please see the main [Boot Repository][1] for more info.
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject tailrecursion/boot.core "2.3.1"
(defproject tailrecursion/boot.core "2.5.0"
:description "A script interpreter for Clojure. Also a build tool."
:url "https://github.com/tailrecursion/boot.core"
:license {:name "Eclipse Public License"
Expand Down
14 changes: 7 additions & 7 deletions src/tailrecursion/boot/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
(let [deps (resolve 'tailrecursion.boot.loader/dependencies)
add! (resolve 'tailrecursion.boot.loader/add-dependencies!)]
(add! new (:repositories env))
(if deps @@deps (into (or old []) new))))
(into (or old []) new)))

(defn- add-directories!
"Add URLs (directories or JAR files) to the classpath."
Expand Down Expand Up @@ -76,8 +76,8 @@
:out-path "out"
:src-paths #{}
:src-static #{}
:repositories #{"http://clojars.org/repo/"
"http://repo1.maven.org/maven2/"}
:repositories {"clojars" "http://clojars.org/repo/"
"central" "http://repo1.maven.org/maven2/"}
:test "test"
:target "target"
:resources "resources"
Expand Down Expand Up @@ -145,7 +145,7 @@
(fn [key old-value new-value env] key) :default ::default)

(defmethod merge-env! ::default [key old new env] new)
(defmethod merge-env! :repositories [key old new env] (into (or old #{}) new))
(defmethod merge-env! :repositories [key old new env] (into (or old {}) (if (set? new) (zipmap new new) new)))
(defmethod merge-env! :src-static [key old new env] (into (or old #{}) new))
(defmethod merge-env! :src-paths [key old new env] (into (or old #{}) new))
(defmethod merge-env! :dependencies [key old new env] (add-dependencies! old new env))
Expand All @@ -169,7 +169,7 @@
(if k (get @boot-env k not-found) @boot-env))

(defn add-sync!
"Specify directories to sync after build event. The `dst` argument is the
"Specify directories to sync after build event. The `dst` argument is the
destination directory. The `srcs` are an optional list of directories whose
contents will be copied into `dst`. The `add-sync!` function is associative.

Expand All @@ -186,7 +186,7 @@

(defn consume-src!
"Tasks use this function to declare that they \"consume\" certain files. Files
in staging directories which are consumed by tasks will not be synced to the
in staging directories which are consumed by tasks will not be synced to the
`:out-path` at the end of the build cycle. The `filter` argument is a function
which will be called with the seq of artifact `java.io.File` objects from the
task staging directories. It should return a seq of files to be comsumed.
Expand Down Expand Up @@ -232,7 +232,7 @@
(tmp/tmpfile? (tmpreg) f))

(defn mktmp!
"Create a temp file and return its `File` object. If `mktmp!` has already
"Create a temp file and return its `File` object. If `mktmp!` has already
been called with the given `key` the tmpfile will be truncated. The optional
`name` argument can be used to customize the temp file name (useful for
creating temp files with a specific file extension, for example)."
Expand Down
27 changes: 17 additions & 10 deletions src/tailrecursion/boot/core/task.clj
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
(recur (core/make-event event)))))

(defn files-changed?
[& [type]]
[& [type quiet?]]
(let [dirs (remove core/tmpfile? (core/get-env :src-paths))
watchers (map file/make-watcher dirs)
since (atom 0)]
Expand All @@ -237,9 +237,13 @@
(clean :hash))]
(if-let [mods (->> (or type :time) (get info) seq)]
(do
(let [path (file/path (first mods))
ok "\033[34m↳ Elapsed time: %6.3f sec ›\033[33m 00:00:00 \033[0m"
fail "\n\033[31m%s\033[0m\n\033[34m↳ Elapsed time: %6.3f sec ›\033[33m 00:00:00 \033[0m"]
(let [path (file/path (first mods))
ok-v "\033[34m↳ Elapsed time: %6.3f sec ›\033[33m 00:00:00 \033[0m"
ok-q "Elapsed time: %6.3f sec\n"
fail-v "\n\033[31m%s\033[0m\n\033[34m↳ Elapsed time: %6.3f sec ›\033[33m 00:00:00 \033[0m"
fail-q "\n%s\nElapsed time: %6.3f sec\n"
ok (if quiet? ok-q ok-v)
fail (if quiet? fail-q fail-v)]
(when (not= 0 @since) (println))
(reset! since (:time event))
(print-time ok fail (continue (assoc event :watch info)))))
Expand All @@ -249,16 +253,19 @@
m (mod (long (/ diff 60)) 60)
h (mod (long (/ diff 3600)) 24)]
(core/sync!)
(printf "\033[33m%s%02d:%02d:%02d \033[0m" pad h m s))))))))
(when-not quiet? (printf "\033[33m%s%02d:%02d:%02d \033[0m" pad h m s)))))))))

(core/deftask watch
"Watch `:src-paths` and call its continuation when files change.

The `:type`option specifies how changes to files are detected and can be
either `:time`or `:hash` (default `:time`). The `:msec` option specifies the
polling interval in milliseconds (default 200)."
[& {:keys [type msec] :or {type :time msec 200}}]
(comp (auto msec) (files-changed? type)))
Options:

:type Specifies how changes to files are detected and can be either
:time or :hash (default :time).
:msec Specifies the polling interval in milliseconds (default 200).
:quiet When set to true no ANSI colors or updating timer are printed. "
[& {:keys [type msec quiet] :or {type :time msec 200 quiet false}}]
(comp (auto msec) (files-changed? type quiet)))

(core/deftask syncdir
"Copy/sync files between directories.
Expand Down
170 changes: 170 additions & 0 deletions src/tailrecursion/boot/core/task/repl.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
(ns tailrecursion.boot.core.task.repl
"Start a repl session with the current project."
(:require [clojure.set :as set]
[clojure.string :as string]
[clojure.java.io :as io]
[tailrecursion.boot.core :as core]))

(def default-cfg
"Default configuration is deep-merged with the options passed to repl task"
{:host "127.0.0.1"
:port 0
:middlewares []
:timeout 60000
:init-ns 'user
:repl-options {:history-file (str (io/file ".repl-history"))
:input-stream System/in}})

(defn deep-merge
"Recursively merges maps. If vals are not maps, chooses last value"
[& vals]
(if (every? map? vals)
(apply merge-with deep-merge vals)
(last vals)))

(def ^:private nrepl-port-file (io/file ".nrepl-port"))

(def ^:private ack-server
(delay ((resolve 'clojure.tools.nrepl.server/start-server)
:bind (:host default-cfg)
:handler ((resolve 'clojure.tools.nrepl.ack/handle-ack)
(resolve 'clojure.tools.nrepl.server/unknown-op)))))

(defn ^:private nrepl-started-msg
[host port]
(str "nREPL server started on port " port " on host " host
" - nrepl://" host ":" port))

(defn ^:private start-server
[{:as cfg
:keys [host middlewares ack-port]}]
(let [headless? (nil? ack-port)
cfg (-> (set/rename-keys cfg {:host :bind})
(assoc :handler (apply (resolve 'clojure.tools.nrepl.server/default-handler) middlewares))
(select-keys [:bind :port :handler :ack-port]))
{:as server :keys [port]} (->> (apply concat cfg)
(apply (resolve 'clojure.tools.nrepl.server/start-server)))]
(when headless?
(println (nrepl-started-msg host port)))
(spit (doto nrepl-port-file .deleteOnExit) port)
@(promise)))

(defn ^:private start-server-in-thread
[{:as cfg
:keys [host timeout]}]
((resolve 'clojure.tools.nrepl.ack/reset-ack-port!))
(let [ack-port (:port @ack-server)]
(-> (bound-fn []
(start-server (assoc cfg :ack-port ack-port)))
(Thread.)
(.start)))
(if-let [repl-port ((resolve 'clojure.tools.nrepl.ack/wait-for-ack) timeout)]
(do (println (nrepl-started-msg host repl-port))
repl-port)
(throw (ex-info "REPL server launch timed out." {}))))

(defn ^:private options-for-reply
[opts & {:keys [attach port]}]
(as-> opts opts
(apply dissoc opts (concat [:init] (if attach [:host :port])))
(merge opts (cond attach {:attach (str attach)}
port {:port port}
:else {}))
(set/rename-keys opts {:prompt :custom-prompt
:welcome :custom-help})
(if (:port opts) (update-in opts [:port] str) opts)))

(defn ^:private client
[{:keys [repl-options]} attach]
((resolve 'reply.main/launch-nrepl)
(options-for-reply repl-options :attach attach)))

(defn ^:private connect-string
[opts]
(as-> (str (first opts)) x
(string/split x #":")
(remove string/blank? x)
(-> (drop-last (count x) [(:host default-cfg)
(try (slurp nrepl-port-file)
(catch Exception _ ""))])
(concat x))
(string/join ":" x)
(if (re-find #":\d+($|/.*$)" x)
x
(throw (ex-info "Port is required" {:connect-string x})))))

(defn wrap-init-ns
[init-ns]
(with-local-vars
[wrap-init-ns'
(fn [h]
;; this needs to be a var, since it's in the nREPL session
(with-local-vars [init-ns-sentinel nil]
(fn [{:keys [session] :as msg}]
(when-not (@session init-ns-sentinel)
(swap! session assoc
(var *ns*)
(try (require init-ns) (create-ns init-ns)
(catch Throwable t (create-ns 'user)))
init-ns-sentinel true))
(h msg))))]
(doto wrap-init-ns'
;; set-descriptor! currently nREPL only accepts a var
((resolve 'clojure.tools.nrepl.middleware/set-descriptor!)
{:requires #{(resolve 'clojure.tools.nrepl.middleware.session/session)}
:expects #{"eval"}})
(alter-var-root (constantly @wrap-init-ns')))))

(defn repl-cfg
([opts] (repl-cfg opts {}))
([opts event]
(as-> (apply hash-map opts) cfg
(deep-merge default-cfg (get event ::config {}) cfg)
(update-in cfg [:middlewares] conj (wrap-init-ns (:init-ns cfg))))))

(defn headless
[opts]
(start-server (repl-cfg opts core/*event*)))

(def ^:private client-mode? (complement #{:headless :pass-through}))

(defn ^:private load-dynamic-dependencies
[[cmd & opts :as args]]
(core/set-env! :dependencies '[[org.clojure/tools.nrepl "0.2.3"]])
(require 'clojure.tools.nrepl.ack)
(require 'clojure.tools.nrepl.server)
(when (client-mode? cmd)
(core/set-env! :dependencies '[[reply "0.3.0"]])
(require 'reply.main)))

(core/deftask repl
"Start a repl session for the current project.

Subcommands:

<none> | [:host \"127.0.0.1\"] [:port random] [:middlewares []] [:init-ns 'user]

:headless [:host host] [:port port] [:middlewares []]
This will launch an nREPL server and wait, rather than connecting
a client to it.

:pass-through [:host host] [:port port] [:middlewares []]
Like :headless, but does not block the task chain.

:connect [dest]
Connects to an already running nREPL server. Dest can be:
- host:port -- connects to the specified host and port;
- port -- host defaults to localhost

If no dest is given, resolves the host as described above
and the port from .nrepl-port in the project root."
[& [cmd & opts :as args]]
(load-dynamic-dependencies args)
(core/with-pre-wrap
(condp = cmd
:connect (client default-cfg (connect-string opts))
:headless (headless opts)
:pass-through (future (headless opts))
(let [cfg (repl-cfg args core/*event*)]
(->> (start-server-in-thread cfg)
(client cfg))))))
51 changes: 45 additions & 6 deletions src/tailrecursion/boot/deps.clj
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,54 @@
(defn ^:deprecated resolve-deps* [coords repos]
(require 'cemerick.pomegranate.aether)
(let [resolve-dependencies (resolve 'cemerick.pomegranate.aether/resolve-dependencies)]
(->> (resolve-dependencies :coordinates coords :repositories (zipmap repos repos))
(kahn/topo-sort)
(map (fn [x] {:dep x :jar (.getPath (:file (meta x)))})))))
(->> (resolve-dependencies :coordinates coords :repositories repos)
(kahn/topo-sort)
(map (fn [x] {:dep x :jar (.getPath (:file (meta x)))})))))

(def ^:private memoized-resolve-deps! (atom nil))

(defn resolve-deps!
"FIXME: look at this function. just look at it."
[]
(require 'tailrecursion.boot.loader)
(require 'tailrecursion.boot.classlojure.core)
(when-let [get-loader (resolve 'tailrecursion.boot.loader/get-classloader)]
(compare-and-set! memoized-resolve-deps! nil
(let [eval-in (resolve 'tailrecursion.boot.classlojure.core/eval-in)]
(memoize
(fn [coords repos]
(eval-in (get-loader)
`(do (require
'[clojure.set :as ~'x-set]
'[cemerick.pomegranate.aether :as ~'x-aether]
'[tailrecursion.boot-classloader :as ~'x-loader])
(let [res# (fn [x#] (x-aether/resolve-dependencies
:coordinates x#
:repositories (zipmap '~repos '~repos)))
sort# (partial group-by (comp zero? count second))
proc# (fn [x#] (->> x# (map (juxt ffirst (comp set (partial map first) second)))))
derp# (fn [x#] (->> x# first vector res#
(filter (comp (partial = (ffirst x#)) ffirst))
first second))
diff# (fn [x# y#] (x-set/difference y# x#))
d# (res# '~coords)
deps# (->> d# (reduce (fn [xs# x#] (assoc xs# (ffirst x#) (first x#))) {}))
dd# (->> d# (map (juxt first derp#)) proc# sort#)
dd# (loop [sorted# (dd# true) unsorted# (dd# false)]
(if-not (seq unsorted#)
(map first sorted#)
(let [set# (set (map first sorted#))
ddd# (->> unsorted#
(map (juxt first (comp (partial diff# set#) second)))
sort#)]
(recur (into sorted# (ddd# true)) (ddd# false)))))]
(->> dd# (map (fn [x#] {:dep (deps# x#) :jar (.getPath (:file (meta (deps# x#))))}))))))))))
@memoized-resolve-deps!))

(defn deps [env]
(require 'tailrecursion.boot.loader)
(let [resolve-deps! (resolve 'tailrecursion.boot.loader/resolve-dependencies!)
{repos :repositories coords :dependencies} @env]
(->> ((or resolve-deps! resolve-deps*) coords repos)
(let [{repos :repositories coords :dependencies} @env]
(->> ((or (resolve-deps!) resolve-deps*) coords repos)
(map :jar)
(filter #(.endsWith % ".jar"))
(map #(JarFile. (io/file %)))
Expand Down