diff --git a/README.md b/README.md index 83dc08f..1b1cfa6 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,9 @@ But there are some important differences: `:subprocess` config option, which defaults to "lein" * lein-modules supports automatic discovery of child modules so that you don't have to set `:dirs` at all -* lein-modules simplifies your release process by eliminating the - redundant references to your project's current version in - interdependent modules, e.g. - `[:dependencies [[your-project/common :version]]`, resolving the - `:version` keyword to the value from the dependent's own project map +* lein-modules resolves interdependent child modules automatically, so + you only need to specify the version of each child once, in its own + `project.clj` ### Release Management @@ -188,9 +186,8 @@ any of the following keys: versions of your child modules' shared dependencies in a single place. And like the `:inherited` profile, when multiple `:versions` maps are found among ancestors, the most immediate take precedence. - The *project* map's `:version` is automatically included in the - `:versions` map, so your interdependent modules may use that without - configuring this option at all. + The versions of all child modules are automatically discovered and + included in this map for you. * `:dirs` - A vector of strings denoting the relative paths to the project's child modules. Normally, they're discovered automatically @@ -267,8 +264,7 @@ version itself will be tried as a key. midje "1.6.0" ring "1.2.1" :jbossas "7.2.x.slim.incremental.12" - org.jboss.as :jbossas - org.immutant :version}}) + org.jboss.as :jbossas}}) ``` ### Child @@ -276,7 +272,7 @@ version itself will be tried as a key. (defproject org.immutant/web "1.0.3-SNAPSHOT" :plugins [[lein-modules "0.3.11"]] :description "The web component" - :dependencies [[org.immutant/core :version] + :dependencies [[org.immutant/core "_"] [ring/ring-servlet "_"] [potemkin "0.3.4"]]) ``` diff --git a/src/lein_modules/common.clj b/src/lein_modules/common.clj index b608239..ab4e380 100644 --- a/src/lein_modules/common.clj +++ b/src/lein_modules/common.clj @@ -1,6 +1,7 @@ (ns lein-modules.common (:require [leiningen.core.project :as prj] [leiningen.core.main :refer (version-satisfies? leiningen-version)] + [leiningen.core.utils :as utils] [clojure.java.io :as io] [lein-modules.compression :refer (compressed-profiles)])) @@ -8,6 +9,18 @@ (load-string "#(prj/init-profiles (prj/project-with-profiles (prj/read-raw %)) [:default])") prj/read)) +(def ^:dynamic *middleware-disabled* false) + +(defmacro without-middleware [& body] + `(binding [*middleware-disabled* true] + ~@body)) + +(defmacro with-middleware [project & body] + `(if (or *middleware-disabled* (-> ~project meta ::middleware-applied)) + ~project + (let [~project (vary-meta ~project assoc ::middleware-applied true)] + (vary-meta (do ~@body) dissoc ::middleware-applied)))) + (defn with-profiles "Apply profiles to project" [project profiles] @@ -36,7 +49,62 @@ "Traverse all parents to accumulate a list of :modules config, ordered by least to most immediate ancestors" [project] - (loop [p project, acc '()] - (if (nil? p) - (remove nil? acc) - (recur (parent p) (conj acc (-> p :modules)))))) + (without-middleware + (loop [p project, acc '()] + (if (nil? p) + (remove nil? acc) + (recur (parent p) (conj acc (-> p :modules))))))) + +(defn id + "Returns fully-qualified symbol identifier for project" + [project] + (if project + (symbol (:group project) (:name project)))) + +(defn child? + "Return true if child is an immediate descendant of project" + [project child] + (= (:root project) (:root (parent child)))) + +(defn file-seq-sans-symlinks + "A tree seq on java.io.Files that aren't symlinks" + [dir] + (tree-seq + (fn [^java.io.File f] (and (.isDirectory f) (not (utils/symlink? f)))) + (fn [^java.io.File d] (seq (.listFiles d))) + dir)) + +(defn children + "Return the child maps for a project according to its active profiles" + [project] + (if-let [dirs (-> project :modules :dirs)] + (remove nil? + (map (comp #(try (read-project %) (catch Exception e (println (.getMessage e)))) + (memfn getCanonicalPath) + #(io/file (:root project) % "project.clj")) + dirs)) + (->> (file-seq-sans-symlinks (io/file (:root project))) + (filter #(= "project.clj" (.getName %))) + (remove #(= (:root project) (.getParent %))) + (map (comp #(try (read-project %) (catch Exception e (println (.getMessage e)))) str)) + (remove nil?) + (filter #(child? project (with-profiles % (compressed-profiles project))))))) + +(defn progeny + "Recursively return the project's children in a map keyed by id" + ([project] + (progeny project (compressed-profiles project))) + ([project profiles] + (let [kids (children (with-profiles project profiles))] + (apply merge + (into {} (map (juxt id identity) kids)) + (->> kids + (remove #(= (:root project) (:root %))) ; in case "." in :dirs + (map #(progeny % profiles))))))) + +(defn primogenitor + "The top-most parent of a project" + [project] + (if-let [p (parent project nil)] + (recur p) + project)) diff --git a/src/lein_modules/inheritance.clj b/src/lein_modules/inheritance.clj index 1076862..5c0fc12 100644 --- a/src/lein_modules/inheritance.clj +++ b/src/lein_modules/inheritance.clj @@ -1,5 +1,5 @@ (ns lein-modules.inheritance - (:use [lein-modules.common :only (config parent)] + (:use [lein-modules.common :only (config parent without-middleware)] [lein-modules.compression :only (compressed-profiles)]) (:require [leiningen.core.project :as prj])) @@ -42,13 +42,14 @@ ([project] (compositize-profiles project (compressed-profiles project))) ([project active-profiles] + (without-middleware (loop [p project, result nil] (if (nil? p) result (recur (parent p active-profiles) (reduce (compositor p) result (conj (select-keys (:modules p) [:inherited]) - (filter-profiles (:profiles (meta p)))))))))) + (filter-profiles (:profiles (meta p))))))))))) (defn inherit "Add profiles from parents, setting any :inherited ones if found, diff --git a/src/lein_modules/plugin.clj b/src/lein_modules/plugin.clj index fd31bb1..726c0ad 100644 --- a/src/lein_modules/plugin.clj +++ b/src/lein_modules/plugin.clj @@ -1,17 +1,15 @@ (ns lein-modules.plugin (:use [lein-modules.versionization :only (versionize)] [lein-modules.inheritance :only (inherit)] - [lein-modules.common :only (config)])) + [lein-modules.common :only (with-middleware)])) (defn middleware "Implicit Leiningen middleware, guarding recursive middleware calls with a metadata flag. See https://github.com/technomancy/leiningen/issues/1151" [project] - (if (-> project meta ::middleware-applied) - project + (with-middleware project (-> project - (vary-meta assoc ::middleware-applied true) inherit - versionize - (vary-meta dissoc ::middleware-applied)))) + versionize))) + diff --git a/src/lein_modules/versionization.clj b/src/lein_modules/versionization.clj index f5b5ff3..1816f18 100644 --- a/src/lein_modules/versionization.clj +++ b/src/lein_modules/versionization.clj @@ -1,6 +1,7 @@ (ns lein-modules.versionization (:use [lein-modules.common :only (config)] - [leiningen.core.project :only (artifact-map)])) + [leiningen.core.project :only (artifact-map)] + [lein-modules.common :only (primogenitor progeny without-middleware)])) (defn versions "Merge dependency management maps of :versions from the @@ -33,11 +34,20 @@ ver) opts))) +(def related-module-versions + "Return a mapping of all related project symbols to their versions" + (memoize + (fn [project] + (without-middleware + (into {} + (for [[k v] (-> project primogenitor progeny)] + [k (:version v)])))))) + (defn versionize "Substitute versions in dependency vectors with actual versions from the :versions modules config" [project] - (let [vmap (merge (select-keys project [:version]) (versions project)) + (let [vmap (merge (related-module-versions project) (versions project)) f #(with-meta (for [d %] (expand-version d vmap)) (meta %))] (-> project (update-in [:dependencies] f) diff --git a/src/leiningen/modules.clj b/src/leiningen/modules.clj index 2198d66..5837249 100644 --- a/src/leiningen/modules.clj +++ b/src/leiningen/modules.clj @@ -5,57 +5,9 @@ [leiningen.core.utils :as utils] [clojure.java.io :as io] [clojure.string :as s]) - (:use [lein-modules.inheritance :only (inherit)] - [lein-modules.common :only (parent with-profiles read-project)] + (:use [lein-modules.common :only (progeny)] [lein-modules.compression :only (compressed-profiles)])) -(defn child? - "Return true if child is an immediate descendant of project" - [project child] - (= (:root project) (:root (parent child)))) - -(defn file-seq-sans-symlinks - "A tree seq on java.io.Files that aren't symlinks" - [dir] - (tree-seq - (fn [^java.io.File f] (and (.isDirectory f) (not (utils/symlink? f)))) - (fn [^java.io.File d] (seq (.listFiles d))) - dir)) - -(defn children - "Return the child maps for a project according to its active profiles" - [project] - (if-let [dirs (-> project :modules :dirs)] - (remove nil? - (map (comp #(try (read-project %) (catch Exception e (println (.getMessage e)))) - (memfn getCanonicalPath) - #(io/file (:root project) % "project.clj")) - dirs)) - (->> (file-seq-sans-symlinks (io/file (:root project))) - (filter #(= "project.clj" (.getName %))) - (remove #(= (:root project) (.getParent %))) - (map (comp #(try (read-project %) (catch Exception e (println (.getMessage e)))) str)) - (remove nil?) - (filter #(child? project (with-profiles % (compressed-profiles project))))))) - -(defn id - "Returns fully-qualified symbol identifier for project" - [project] - (if project - (symbol (:group project) (:name project)))) - -(defn progeny - "Recursively return the project's children in a map keyed by id" - ([project] - (progeny project (compressed-profiles project))) - ([project profiles] - (let [kids (children (with-profiles project profiles))] - (apply merge - (into {} (map (juxt id identity) kids)) - (->> kids - (remove #(= (:root project) (:root %))) ; in case "." in :dirs - (map #(progeny % profiles))))))) - (defn interdependence "Turn a progeny map (symbols to projects) into a mapping of projects to their dependent projects" diff --git a/test-resources/grandparent/parent/child/project.clj b/test-resources/grandparent/parent/child/project.clj index 62a39db..1e66638 100644 --- a/test-resources/grandparent/parent/child/project.clj +++ b/test-resources/grandparent/parent/child/project.clj @@ -9,6 +9,6 @@ [foo/b _] [foo/c _] [foo/d :ver] - [sibling "0.1.0-SNAPSHOT"]] + [sibling _]] :modules {:versions {parent "3.0"}} :profiles {:by-child {}}) diff --git a/test-resources/grandparent/parent/sibling/project.clj b/test-resources/grandparent/parent/sibling/project.clj index b465b15..1bcd6d7 100644 --- a/test-resources/grandparent/parent/sibling/project.clj +++ b/test-resources/grandparent/parent/sibling/project.clj @@ -1,4 +1,4 @@ -(defproject sibling "0.1.0-SNAPSHOT" +(defproject sibling "0.2.0-SNAPSHOT" :description "sibling" :profiles {:by-child {:modules {:parent nil}}}) diff --git a/test/lein_modules/versionization_test.clj b/test/lein_modules/versionization_test.clj index cb60c11..f24970c 100644 --- a/test/lein_modules/versionization_test.clj +++ b/test/lein_modules/versionization_test.clj @@ -15,7 +15,8 @@ '[foo/a "1"] '[foo/b "2"] '[foo/c "3"] - '[foo/d "2.0"]))) + '[foo/d "2.0"] + '[sibling/sibling "0.2.0-SNAPSHOT"]))) (deftest partial-composites-should-work (let [p (-> (prj/read "test-resources/lambda/project.clj") diff --git a/test/leiningen/modules_test.clj b/test/leiningen/modules_test.clj index 1c12e44..223c16b 100644 --- a/test/leiningen/modules_test.clj +++ b/test/leiningen/modules_test.clj @@ -1,7 +1,7 @@ (ns leiningen.modules-test (:use clojure.test leiningen.modules - [lein-modules.common :only (parent)] + [lein-modules.common :only (parent child? children progeny)] [lein-modules.inheritance :only (inherit)]) (:require [leiningen.core.project :as prj] [leiningen.clean :refer [delete-file-recursively]]