From 9f37f53c5ec4cd7cae453faf4d76edfc07b5b429 Mon Sep 17 00:00:00 2001 From: Justin Balthrop Date: Mon, 8 Oct 2012 18:02:58 -0700 Subject: [PATCH 1/2] move dependency and repository merging into meta-merge This is accomplished with the :reduce metadata, which specifies the reduce function to use when merging. This allows us to merge dependencies and repositories deeply like other structures. Note that dependencies are transformed into a map before they are merged and then transformed back into a vector. Also change the way that collections are merged. They used to be merged by taking the right collection and prepending it to the left collection. This behavior was needed for :*-paths in defproject, but it is not an obvious default. Now, the default is to append the right collection, but the :prepend metadata can be used to tell meta-merge to prepend instead. By default, :source-paths, :resource-paths and :test-paths have :prepend set to true. --- leiningen-core/project.clj | 1 + leiningen-core/src/leiningen/core/project.clj | 316 ++++++++++-------- .../test/leiningen/core/test/project.clj | 160 +++++---- src/leiningen/pom.clj | 17 +- src/leiningen/search.clj | 2 +- src/leiningen/trampoline.clj | 3 +- test/leiningen/test/pom.clj | 44 ++- 7 files changed, 292 insertions(+), 251 deletions(-) diff --git a/leiningen-core/project.clj b/leiningen-core/project.clj index 01700a921..526f39e43 100644 --- a/leiningen-core/project.clj +++ b/leiningen-core/project.clj @@ -6,6 +6,7 @@ :dependencies [[org.clojure/clojure "1.4.0"] [bultitude "0.1.7"] [classlojure "0.6.6"] + [useful "0.8.6"] [robert/hooke "1.1.2"] [com.cemerick/pomegranate "0.0.13" :exclusions [org.slf4j/slf4j-api]]] diff --git a/leiningen-core/src/leiningen/core/project.clj b/leiningen-core/src/leiningen/core/project.clj index 271003371..dee65ac06 100755 --- a/leiningen-core/src/leiningen/core/project.clj +++ b/leiningen-core/src/leiningen/core/project.clj @@ -9,12 +9,62 @@ [leiningen.core.utils :as utils] [leiningen.core.ssl :as ssl] [leiningen.core.user :as user] - [leiningen.core.classpath :as classpath]) + [leiningen.core.classpath :as classpath] + [useful.fn :refer [fix]] + [useful.seq :refer [update-first]] + [useful.map :refer [update update-each]]) (:import (clojure.lang DynamicClassLoader) (java.io PushbackReader))) ;; # Project definition and normalization +(defn artifact-map + [id] + {:artifact-id (name id) + :group-id (or (namespace id) (name id))}) + +(defn exclusion-map + "Transform an exclusion vector into a map that is easier to combine with + meta-merge. This allows a profile to override specific exclusion options." + [spec] + (when-let [[id & {:as opts}] (fix spec symbol? vector)] + (-> opts + (merge (artifact-map id)) + (with-meta (meta spec))))) + +(defn exclusion-vec + "Transform an exclusion map back into a vector of the form: + [name/group & opts]" + [exclusion] + (when-let [{:keys [artifact-id group-id]} exclusion] + (into [(symbol group-id artifact-id)] + (apply concat (dissoc exclusion :artifact-id :group-id))))) + +(defn dependency-map + "Transform a dependency vector into a map that is easier to combine with + meta-merge. This allows a profile to override specific dependency options." + [dep] + (when-let [[id version & {:as opts}] dep] + (-> opts + (merge (artifact-map id)) + (assoc :version version) + (update :exclusions #(when % (map exclusion-map %))) + (with-meta (meta dep))))) + +(defn dependency-vec + "Transform a dependency map back into a vector of the form: + [name/group \"version\" & opts]" + [dep] + (when-let [{:keys [artifact-id group-id version]} dep] + (-> dep + (update :exclusions #(when % (map exclusion-vec %))) + (dissoc :artifact-id :group-id :version) + (->> (apply concat) + (into [(symbol group-id artifact-id) version])) + (with-meta (meta dep))))) + +(declare meta-merge) + (defn- unquote-project "Inside defproject forms, unquoting (~) allows for arbitrary evaluation." [args] @@ -26,23 +76,61 @@ identity args)) -(def defaults {:source-paths ["src"] - :resource-paths ["resources"] - :test-paths ["test"] - :native-path "target/native" - :compile-path "target/classes" - :target-path "target" - :prep-tasks ["javac" "compile"] - :repositories [["central" {:url "http://repo1.maven.org/maven2/"}] - ;; TODO: point to releases-only before 2.0 is out - ["clojars" {:url "https://clojars.org/repo/"}]] - :deploy-repositories [["clojars" {:url "https://clojars.org/repo/" - :username :gpg - :password :gpg}]] - :jar-exclusions [#"^\."] - :jvm-opts ["-XX:+TieredCompilation"] - :certificates ["clojars.pem"] - :uberjar-exclusions [#"(?i)^META-INF/[^/]*\.(SF|RSA|DSA)$"]}) +(def defaults + {:source-paths ["src"] + :resource-paths ["resources"] + :test-paths ["test"] + :native-path "target/native" + :compile-path "target/classes" + :target-path "target" + :prep-tasks ["javac" "compile"] + :jar-exclusions [#"^\."] + :jvm-opts ["-XX:+TieredCompilation"] + :certificates ["clojars.pem"] + :uberjar-exclusions [#"(?i)^META-INF/[^/]*\.(SF|RSA|DSA)$"]}) + +(defn- dep-key + "The unique key used to dedupe dependencies." + [[id version & opts]] + (-> (apply hash-map opts) + (select-keys [:classifier :extension]) + (assoc :id id))) + +(defn- add-dep [deps dep] + (let [k (dep-key dep)] + (update-first deps #(= k (dep-key %)) + (fn [existing] + (dependency-vec + (meta-merge (dependency-map existing) + (dependency-map dep))))))) + +(defn- add-repo [repos repo] + (let [[id opts] repo + opts (fix opts string? (partial hash-map :url))] + (update-first repos #(= id (first %)) + (fn [[_ existing]] + [id (meta-merge existing opts)])))) + +(def empty-dependencies + (with-meta [] {:reduce add-dep})) + +(def empty-repositories + (with-meta [] {:reduce add-repo})) + +(def empty-paths + (with-meta [] {:prepend true})) + +(def default-repositories + (with-meta + [["central" {:url "http://repo1.maven.org/maven2/"}] + ;; TODO: point to releases-only before 2.0 is out + ["clojars" {:url "https://clojars.org/repo/"}]] + {:reduce add-repo})) + +(def deploy-repositories + (with-meta + [["clojars" {:url "https://clojars.org/repo/", :password :gpg, :username :gpg}]] + {:reduce add-repo})) (defn make ([project project-name version root] @@ -53,12 +141,27 @@ :version version :root root))) ([project] - (-> (merge defaults project) - (dissoc :eval-in-leiningen :omit-default-repositories) - (assoc :eval-in (or (:eval-in project) - (if (:eval-in-leiningen project) - :leiningen, :subprocess)) - :offline? (not (nil? (System/getenv "LEIN_OFFLINE"))))))) + (let [repos (if (:omit-default-repositories project) + (do (println "WARNING:" + ":omit-default-repositories is deprecated;" + "use :repositories ^:replace [...] instead.") + empty-repositories) + default-repositories)] + (meta-merge + {:repositories repos + :plugin-repositories repos + :deploy-repositories deploy-repositories + :plugins empty-dependencies + :dependencies empty-dependencies + :source-paths empty-paths + :resource-paths empty-paths + :test-paths empty-paths} + (-> (merge defaults project) + (dissoc :eval-in-leiningen :omit-default-repositories) + (assoc :eval-in (or (:eval-in project) + (if (:eval-in-leiningen project) + :leiningen, :subprocess)) + :offline? (not (nil? (System/getenv "LEIN_OFFLINE"))))))))) (defmacro defproject "The project.clj file must either def a project map or call this macro. @@ -69,73 +172,20 @@ (def ~'project (make args# '~project-name ~version root#)))) -(defn- de-dupe-repo [[repositories seen?] [id settings]] - ;; repositories from user profiles can be just credentials, so check :url - (if (or (seen? id) (not (:url settings))) - [repositories seen?] - [(conj repositories [id settings]) (conj seen? id)])) - -(defn- mapize-settings [repositories] - (for [[id settings] repositories] - [id (if (string? settings) {:url settings} settings)])) - -(defn normalize-repos [project] - ;; TODO: got to be a way to tidy this up - (let [project (update-in project [:repositories] mapize-settings) - project (if (:deploy-repositories project) - (update-in project [:deploy-repositories] mapize-settings) - project) - project (if (:plugin-repositories project) - (update-in project [:plugin-repositories] mapize-settings) - project)] - (assoc project :repositories - (first (reduce de-dupe-repo - (if-not (:omit-default-repositories project) - [(:repositories defaults) - (set (map first (:repositories defaults)))] - [[] #{}]) (:repositories project)))))) - -(defn- without-version [[id version & other]] - (-> (apply hash-map other) - (select-keys [:classifier :extension]) - (assoc :id id))) - -(defn- dedupe-step [[deps seen] x] - (if (seen (without-version x)) - ;; this would be so much cleaner if we could just re-use profile-merge - ;; logic, but since :dependencies are a vector, the :replace/:displace - ;; calculations don't apply to nested vectors inside :dependencies. - (let [[seen-dep] (filter #(= (first %) (first x)) deps)] - (if (or (:displace (meta seen-dep)) (:replace (meta x))) - [(assoc deps (.indexOf deps seen-dep) x) seen] - [deps seen])) - [(conj deps x) (conj seen (without-version x))])) - -(defn- dedupe-deps [deps] - (first (reduce dedupe-step [[] #{}] deps))) - -(defn- exclude [exclusions deps dep] - (conj deps - (if (empty? exclusions) - dep - (let [exclusions-offset (.indexOf dep :exclusions)] - (if (pos? exclusions-offset) - (update-in dep [(inc exclusions-offset)] - (comp vec distinct (partial into exclusions))) - (-> dep - (conj :exclusions) - (conj exclusions))))))) - -(defn- add-exclusions [deps exclusions] - (reduce (partial exclude exclusions) [] deps)) - -(defn normalize-deps [project] - (-> project - (update-in [:dependencies] dedupe-deps) - (update-in [:dependencies] add-exclusions (:exclusions project)))) - -(defn normalize-plugins [project] - (update-in project [:plugins] dedupe-deps)) +(defn- add-exclusions [exclusions dep] + (dependency-vec + (meta-merge (dependency-map dep) + {:exclusions (map exclusion-map exclusions)}))) + +(defn- add-global-exclusions [project] + (let [{:keys [dependencies exclusions]} project] + (if-let [exclusions (and (seq dependencies) (seq exclusions))] + (assoc project + :dependencies (with-meta + (mapv (partial add-exclusions exclusions) + dependencies) + (meta dependencies))) + project))) (defn- absolutize [root path] (str (if (.isAbsolute (io/file path)) @@ -152,17 +202,7 @@ :else project)) (defn absolutize-paths [project] - (let [project (reduce absolutize-path project (keys project))] - (assoc project :compile-path (or (:compile-path project) - (str (io/file (:target-path project) - "classes")))))) - -(defn remove-aliases [project] - (dissoc project :deps :eval-in-leiningen)) - -(def ^{:arglists '([project])} normalize - "Normalize project map to standard representation." - (comp normalize-repos normalize-deps absolutize-paths remove-aliases)) + (reduce absolutize-path project (keys project))) ;; # Profiles: basic merge logic @@ -175,7 +215,7 @@ (def default-profiles "Profiles get merged into the project map. The :dev, :provided, and :user profiles are active by default." - (atom {:default [:dev :provided :user :base] + (atom {:default [:base :user :provided :dev] :base {:resource-paths ["dev-resources"] :plugins [['lein-newnew "0.3.5"]] :checkout-deps-shares [:source-paths @@ -188,32 +228,49 @@ (defn- meta-merge "Recursively merge values based on the information in their metadata." - [result latter] - (cond (-> result meta :displace) - latter + [left right] + (cond (or (-> left meta :displace) + (-> right meta :replace)) + (with-meta right + (merge (-> left meta (dissoc :displace)) + (-> right meta (dissoc :replace)))) - (-> latter meta :replace) - latter + (-> left meta :reduce) + (-> left meta :reduce + (reduce left right) + (with-meta (meta left))) - ;; TODO: last-wins breaks here - (and (map? result) (map? latter)) - (merge-with meta-merge result latter) + (nil? left) right + (nil? right) left - (and (set? result) (set? latter)) - (set/union latter result) + (and (map? left) (map? right)) + (merge-with meta-merge left right) - (and (coll? result) (coll? latter)) - (concat latter result) + (and (set? left) (set? right)) + (set/union right left) - (= (class result) (class latter)) latter + (and (coll? left) (coll? right)) + (if (or (-> left meta :prepend) + (-> right meta :prepend)) + (-> (concat right left) + (with-meta (merge (meta left) + (select-keys (meta right) [:displace])))) + (concat left right)) - :else (doto latter (println "has a type mismatch merging profiles.")))) + (= (class left) (class right)) right + + :else + (do (println left "and" right "have a type mismatch merging profiles.") + right))) (defn- apply-profiles [project profiles] ;; We reverse because we want profile values to override the project, so we ;; need "last wins" in the reduce, but we want the first profile specified by ;; the user to take precedence. - (reduce (partial merge-with meta-merge) + (reduce (fn [project profile] + (with-meta + (meta-merge project profile) + (meta-merge (meta project) (meta profile)))) project (reverse profiles))) @@ -266,18 +323,11 @@ (when-not (pomegranate/modifiable-classloader? cl) (.setContextClassLoader thread (DynamicClassLoader. cl))))) -(defn- merge-plugin-repositories [project] - (if-let [pr (:plugin-repositories project)] - (if (:omit-default-repositories project) - (assoc project :repositories pr) - (update-in project [:repositories] concat pr)) - project)) - (defn load-plugins ([project key] (when (seq (get project key)) (ensure-dynamic-classloader) - (classpath/resolve-dependencies key (merge-plugin-repositories project) + (classpath/resolve-dependencies key project :add-classpath? true)) (doseq [wagon-file (-> (.getContextClassLoader (Thread/currentThread)) (.getResources "leiningen/wagons.clj") @@ -290,9 +340,8 @@ (defn plugin-vars [project type] (for [[plugin _ & {:as opts}] (:plugins project) :when (get opts type true)] - (with-meta (symbol (str (name plugin) ".plugin") - (name type)) - {:optional true}))) + (-> (symbol (str (name plugin) ".plugin") (name type)) + (with-meta {:optional true})))) (defn- plugin-hooks [project] (plugin-vars project :hooks)) @@ -354,13 +403,14 @@ "Compute a fresh version of the project map, including and excluding the specified profiles." [project include-profiles & [exclude-profiles]] - (let [without-profiles (:without-profiles (meta project) project) + (let [project (:without-profiles (meta project) project) profile-map (apply dissoc (read-profiles project) exclude-profiles) profiles (map (partial lookup-profile profile-map) include-profiles)] - (-> without-profiles + (-> project (apply-profiles profiles) - (normalize) - (vary-meta merge {:without-profiles without-profiles + (absolutize-paths) + (add-global-exclusions) + (vary-meta merge {:without-profiles project :included-profiles include-profiles :excluded-profiles exclude-profiles})))) diff --git a/leiningen-core/test/leiningen/core/test/project.clj b/leiningen-core/test/leiningen/core/test/project.clj index 0a725620d..a09209af1 100755 --- a/leiningen-core/test/leiningen/core/test/project.clj +++ b/leiningen-core/test/leiningen/core/test/project.clj @@ -28,11 +28,11 @@ :eval-in :leiningen, :license {:name "Eclipse Public License"} - :dependencies '[[leiningen-core "2.0.0-SNAPSHOT"] - [clucy "0.2.2" :exclusions [org.clojure/clojure]] - [lancet "1.0.1"] + :dependencies '[[leiningen-core/leiningen-core "2.0.0-SNAPSHOT"] + [clucy/clucy "0.2.2" :exclusions [[org.clojure/clojure]]] + [lancet/lancet "1.0.1"] [robert/hooke "1.1.2"] - [stencil "0.2.0"]], + [stencil/stencil "0.2.0"]], :twelve 12 ; testing unquote :repositories [["central" {:url "http://repo1.maven.org/maven2/"}] @@ -41,73 +41,82 @@ (deftest test-read-project (let [actual (read (.getFile (io/resource "p1.clj")))] (doseq [[k v] expected] - (is (= (k actual) v))) + (is (= v (k actual)))) (doseq [[k path] paths :when (string? path)] - (is (= (k actual) (str (:root actual) "/" path)))) + (is (= (str (:root actual) "/" path) + (k actual)))) (doseq [[k path] paths :when (coll? path)] - (is (= (k actual) (for [p path] (str (:root actual) "/" p))))))) + (is (= (for [p path] (str (:root actual) "/" p)) + (k actual)))))) ;; TODO: test omit-default ;; TODO: test reading project that doesn't def project (def test-profiles (atom {:qa {:resource-paths ["/etc/myapp"]} :test {:resource-paths ["test/hi"]} - :repl {:dependencies '[[org.clojure/tools.nrepl - "0.2.0-beta6" - :exclusions - [org.clojure/clojure]] - [org.thnetos/cd-client "0.3.4" - :exclusions - [org.clojure/clojure]]]} + :repl {:dependencies + '[[org.clojure/tools.nrepl "0.2.0-beta6" + :exclusions [org.clojure/clojure]] + [org.thnetos/cd-client "0.3.4" + :exclusions [org.clojure/clojure]]]} :tes :test :dev {:test-paths ["test"]}})) (deftest test-merge-profile-paths (with-redefs [default-profiles test-profiles] (is (= ["/etc/myapp" "test/hi" "blue-resources" "resources"] - (-> {:resource-paths ["resources"] - :profiles {:blue {:resource-paths ["blue-resources"]}}} + (-> (make + {:resource-paths ["resources"] + :profiles {:blue {:resource-paths ["blue-resources"]}}}) (merge-profiles [:qa :tes :blue]) :resource-paths))) (is (= ["/etc/myapp" "test/hi" "blue-resources"] - (-> {:resource-paths ^:displace ["resources"] - :profiles {:blue {:resource-paths ["blue-resources"]}}} + (-> (make + {:resource-paths ^:displace ["resources"] + :profiles {:blue {:resource-paths ["blue-resources"]}}}) (merge-profiles [:qa :tes :blue]) :resource-paths))) (is (= ["replaced"] - (-> {:resource-paths ["resources"] - :profiles {:blue {:resource-paths ^:replace ["replaced"]}}} + (-> (make + {:resource-paths ["resources"] + :profiles {:blue {:resource-paths ^:replace ["replaced"]}}}) (merge-profiles [:blue :qa :tes]) :resource-paths))) (is (= {:url "http://" :username "u" :password "p"} - (-> {:repositories [["foo" {:url "http://" :creds :gpg}]] - :profiles {:blue {:repositories {"foo" - ^:replace {:url "http://" - :username "u" - :password "p"}}}}} + (-> (make + {:repositories [["foo" {:url "http://" :creds :gpg}]] + :profiles {:blue {:repositories {"foo" + ^:replace {:url "http://" + :username "u" + :password "p"}}}}}) (merge-profiles [:blue :qa :tes]) :repositories last last))))) -;; TODO (deftest test-merge-profile-deps (with-redefs [default-profiles test-profiles] - (let [project {:resource-paths ["resources"] - :profiles {:dev {:dependencies - '[^:displace [org.thnetos/cd-client "0.3.0"] - [org.clojure/tools.nrepl "0.2.0-beta2"]]}}} - cp (-> (merge-profiles project [:dev :repl]) - (classpath/get-classpath))] - (is (some (partial re-find #"nrepl-0.2.0-beta2") cp)) - (is (some (partial re-find #"cd-client-0.3.4") cp))))) + (let [project (make + {:resource-paths ["resources"] + :dependencies '[^:displace [org.foo/bar "0.1.0" :foo [1 2]] + [org.foo/baz "0.2.0" :foo [1 2]] + [org.foo/zap "0.3.0" :foo [1 2]]] + :profiles {:dev {:dependencies + '[[org.foo/bar "0.1.2"] + [org.foo/baz "0.2.1"] + ^:replace [org.foo/zap "0.3.1"]]}}})] + (is (= '[[org.foo/bar "0.1.2"] + [org.foo/baz "0.2.1" :foo [1 2]] + [org.foo/zap "0.3.1"]] + (-> (merge-profiles project [:dev]) + :dependencies)))))) (deftest test-global-exclusions - (is (= '[[org.clojure/clojure] - [org.clojure/clojure pomegranate] - [org.clojure/clojure]] - (map #(:exclusions (apply hash-map %)) + (is (= '[[[org.clojure/clojure]] + [[pomegranate/pomegranate] [org.clojure/clojure]] + [[org.clojure/clojure]]] + (map #(distinct (:exclusions (apply hash-map %))) (-> {:dependencies '[[lancet "1.0.1"] [leiningen-core "2.0.0-SNAPSHOT" :exclusions [pomegranate]] @@ -158,66 +167,51 @@ :without-profiles))))) (deftest test-merge-anon-profiles - (let [expected-result {:A 1 :C 3 :profiles {:a {:A 1} - :b {:B 2}} - :repositories [["central" {:url "http://repo1.maven.org/maven2/"}] - ["clojars" {:url "https://clojars.org/repo/"}]] - :dependencies [], :compile-path "classes"}] - (is (= expected-result - (-> {:profiles {:a {:A 1} :b {:B 2}}} - (merge-profiles [:a {:C 3}])))))) + (is (= {:A 1, :C 3} + (-> {:profiles {:a {:A 1} :b {:B 2}}} + (merge-profiles [:a {:C 3}]) + (dissoc :profiles))))) (deftest test-composite-profiles - (let [expected-result {:A '(2 3 1), :B 2, :C 3, - :repositories [["central" {:url "http://repo1.maven.org/maven2/"}] - ["clojars" {:url "https://clojars.org/repo/"}]] - :dependencies [], :compile-path "classes"}] - (is (= expected-result - (-> {:profiles {:a [:c :b] - :b [:d {:A [1] :B 1 :C 1}] - :c {:A [2] :B 2} - :d {:A [3] :C 3}}} - (merge-profiles [:a]) - (dissoc :profiles)))))) + (is (= {:A '(1 3 2), :B 2, :C 3} + (-> {:profiles {:a [:c :b] + :b [:d {:A [1] :B 1 :C 1}] + :c {:A [2] :B 2} + :d {:A [3] :C 3}}} + (merge-profiles [:a]) + (dissoc :profiles))))) (deftest test-override-default - (let [expected-result {:A 1, :B 2, :C 3 - :repositories [["central" {:url "http://repo1.maven.org/maven2/"}] - ["clojars" {:url "https://clojars.org/repo/"}]] - :dependencies [], :compile-path "classes"}] - (is (= expected-result - (-> {:profiles {:a {:A 1 :B 2} - :b {:B 2 :C 2} - :c {:C 3} - :default [:c :b :a]}} - (merge-profiles [:default]) - (dissoc :profiles)))))) + (is (= {:A 1, :B 2, :C 3} + (-> {:profiles {:a {:A 1 :B 2} + :b {:B 2 :C 2} + :c {:C 3} + :default [:c :b :a]}} + (merge-profiles [:default]) + (dissoc :profiles))))) (deftest test-unmerge-profiles - (let [expected-result {:A 1 :C 3 :profiles {:a {:A 1} - :b {:B 2} - :c {:C 3}} - :repositories [["central" {:url "http://repo1.maven.org/maven2/"}] - ["clojars" {:url "https://clojars.org/repo/"}]] - :dependencies [], :compile-path "classes"}] - (is (= expected-result + (let [expected {:A 1 :C 3}] + (is (= expected (-> {:profiles {:a {:A 1} :b {:B 2} :c {:C 3}}} (merge-profiles [:a :b :c]) - (unmerge-profiles [:b])))) - (is (= expected-result + (unmerge-profiles [:b]) + (dissoc :profiles)))) + (is (= expected (-> {:profiles {:a {:A 1} :b {:B 2} :c {:C 3}}} (merge-profiles [:a :b :c {:D 4}]) - (unmerge-profiles [:b {:D 4}])))))) + (unmerge-profiles [:b {:D 4}]) + (dissoc :profiles)))))) (deftest test-dedupe-deps - (is (= '[[org.clojure/clojure "1.4.0"] + (is (= '[[org.clojure/clojure "1.3.0"] [org.clojure/clojure "1.3.0" :classifier "sources"]] - (-> {:dependencies '[[org.clojure/clojure "1.4.0"] - [org.clojure/clojure "1.3.0" :classifier "sources"] - [org.clojure/clojure "1.3.0"]]} - (normalize-deps) + (-> (make + {:dependencies '[[org.clojure/clojure "1.4.0"] + [org.clojure/clojure "1.3.0" :classifier "sources"] + [org.clojure/clojure "1.3.0"]]}) (:dependencies))))) \ No newline at end of file diff --git a/src/leiningen/pom.clj b/src/leiningen/pom.clj index 706d57bfe..4aab701b8 100644 --- a/src/leiningen/pom.clj +++ b/src/leiningen/pom.clj @@ -177,6 +177,13 @@ [key (name val)]))] [:licenses [:license tags]])))) +(defn- resource-tags [project type] + (if-let [resources (seq (:resource-paths project))] + (let [types (keyword (str (name type) "s"))] + (vec (concat [types] + (for [resource resources] + [type [:directory resource]])))))) + (defmethod xml-tags ::build ([_ [project test-project]] (let [[src & extra-src] (concat (:source-paths project) @@ -185,14 +192,8 @@ [:build [:sourceDirectory src] (xml-tags :testSourceDirectory test) - (if-let [resources (seq (:resource-paths project))] - (vec (concat [:resources] - (for [resource resources] - [:resource [:directory resource]])))) - (if-let [resources (seq (:resource-paths test-project))] - (vec (concat [:testResources] - (for [resource resources] - [:testResource [:directory resource]])))) + (resource-tags project :resource) + (resource-tags test-project :testResource) (if-let [extensions (seq (:extensions project))] (vec (concat [:extensions] (for [[dep version] extensions] diff --git a/src/leiningen/search.clj b/src/leiningen/search.clj index cc2624d15..fd8a2300d 100644 --- a/src/leiningen/search.clj +++ b/src/leiningen/search.clj @@ -141,7 +141,7 @@ Also accepts a second parameter for fetching successive pages." ;; Maven's indexer requires over 1GB of free space for a <100M index (let [orig-tmp (System/getProperty "java.io.tmpdir") new-tmp (io/file (user/leiningen-home) "indices" "tmp") - project (or project (project/normalize-repos project/defaults)) + project (or project (project/make {})) contexts (doall (map add-context (:repositories project)))] (try (.mkdirs new-tmp) diff --git a/src/leiningen/trampoline.clj b/src/leiningen/trampoline.clj index 52cecafe2..5b69ee283 100644 --- a/src/leiningen/trampoline.clj +++ b/src/leiningen/trampoline.clj @@ -25,8 +25,7 @@ rests (mapcat rest forms) ;; This won't pick up :jvm-args that come from profiles, but it ;; at least gets us :dependencies. - project (project/normalize-deps (assoc project :dependencies - (apply concat deps))) + project (project/merge-profiles project {:dependencies deps}) command (eval/shell-command project (concat '(do) inits rests))] (string/join " " (if (win-batch?) (map quote-arg command) diff --git a/test/leiningen/test/pom.clj b/test/leiningen/test/pom.clj index 6ff6dc8b9..32a2d1d61 100644 --- a/test/leiningen/test/pom.clj +++ b/test/leiningen/test/pom.clj @@ -33,14 +33,10 @@ ([project profile] (with-profile project :test-pom profile)) ([project name profile] - (let [{:keys [included-profiles - without-profiles]} (meta project)] - (-> without-profiles - (update-in [:profiles] #(assoc % name profile)) - (project/merge-profiles - (if (some #{name} included-profiles) - included-profiles - (conj included-profiles name))))))) + (-> project + (vary-meta update-in [:without-profiles :profiles] + assoc name profile) + (project/merge-profiles [name])))) (deftest test-pom-default-values (let [xml (xml/parse-str (make-pom sample-project))] @@ -179,37 +175,37 @@ [[ring-mock :classifier "cla" :extension "dom"]]]]})))] - (is (= ["peridot" "org.clojure" "rome" "ring"] + (is (= ["org.clojure" "rome" "ring" "peridot"] (map #(first-in % [:dependency :groupId]) (deep-content xml [:project :dependencies])))) - (is (= [ "peridot" "clojure" "rome" "ring"] + (is (= ["clojure" "rome" "ring" "peridot"] (map #(first-in % [:dependency :artifactId]) (deep-content xml [:project :dependencies])))) - (is (= ["0.0.5" "1.3.0" "0.9" "1.0.0"] + (is (= ["1.3.0" "0.9" "1.0.0" "0.0.5"] (map #(first-in % [:dependency :version]) (deep-content xml [:project :dependencies])))) - (is (= ["provided" nil nil nil] + (is (= [nil nil nil "provided"] (map #(first-in % [:dependency :scope]) (deep-content xml [:project :dependencies])))) - (is (= ["true" nil nil nil] + (is (= [nil nil nil "true"] (map #(first-in % [:dependency :optional]) (deep-content xml [:project :dependencies])))) - (is (= ["sources" nil nil nil] + (is (= [nil nil nil "sources"] (map #(first-in % [:dependency :classifier]) (deep-content xml [:project :dependencies])))) - (is (= ["pom" nil nil nil] + (is (= [nil nil nil "pom"] (map #(first-in % [:dependency :type]) (deep-content xml [:project :dependencies])))) - (is (= ["ring-mock" nil nil nil] + (is (= [nil nil nil "ring-mock"] (map #(first-in % [:dependency :exclusions :exclusion :artifactId]) (deep-content xml [:project :dependencies])))) - (is (= ["ring-mock" nil nil nil] + (is (= [nil nil nil "ring-mock"] (map #(first-in % [:dependency :exclusions :exclusion :groupId]) (deep-content xml [:project :dependencies])))) - (is (= ["cla" nil nil nil] + (is (= [nil nil nil "cla"] (map #(first-in % [:dependency :exclusions :exclusion :classifier]) (deep-content xml [:project :dependencies])))) - (is (= ["dom" nil nil nil] + (is (= [nil nil nil "dom"] (map #(first-in % [:dependency :exclusions :exclusion :type]) (deep-content xml [:project :dependencies]))))) (let [xml (xml/parse-str @@ -219,19 +215,19 @@ :scope "provided" :exclusions [ring-mock]]]})))] - (is (= ["peridot" "org.clojure" "rome" "ring"] + (is (= ["org.clojure" "rome" "ring" "peridot"] (map #(first-in % [:dependency :groupId]) (deep-content xml [:project :dependencies])))) - (is (= [ "peridot" "clojure" "rome" "ring"] + (is (= ["clojure" "rome" "ring" "peridot"] (map #(first-in % [:dependency :artifactId]) (deep-content xml [:project :dependencies])))) - (is (= ["0.0.5" "1.3.0" "0.9" "1.0.0"] + (is (= ["1.3.0" "0.9" "1.0.0" "0.0.5"] (map #(first-in % [:dependency :version]) (deep-content xml [:project :dependencies])))) - (is (= ["provided" nil nil nil] + (is (= [nil nil nil "provided"] (map #(first-in % [:dependency :scope]) (deep-content xml [:project :dependencies])))) - (is (= ["ring-mock" nil nil nil] + (is (= [nil nil nil "ring-mock"] (map #(first-in % [:dependency :exclusions :exclusion :artifactId]) (deep-content xml [:project :dependencies])))) (is (= [nil nil nil nil] From 0e02ad7164f28772ee115b551ec0b98a49b8bf59 Mon Sep 17 00:00:00 2001 From: Justin Balthrop Date: Mon, 8 Oct 2012 18:26:25 -0700 Subject: [PATCH 2/2] switch with-profile to last-wins This patch switches with-profile so that the last profile specified wins. This is more in line with the way clojure.core/merge works. For more info, see: http://librelist.com/browser/leiningen/2012/9/12/changing-with-profile-from-first-wins-to-last-wins/#ff32b118546fa95d0960f2247e190c18 --- leiningen-core/src/leiningen/core/project.clj | 5 +---- .../test/leiningen/core/test/project.clj | 14 +++++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/leiningen-core/src/leiningen/core/project.clj b/leiningen-core/src/leiningen/core/project.clj index dee65ac06..f20c4c005 100755 --- a/leiningen-core/src/leiningen/core/project.clj +++ b/leiningen-core/src/leiningen/core/project.clj @@ -264,15 +264,12 @@ right))) (defn- apply-profiles [project profiles] - ;; We reverse because we want profile values to override the project, so we - ;; need "last wins" in the reduce, but we want the first profile specified by - ;; the user to take precedence. (reduce (fn [project profile] (with-meta (meta-merge project profile) (meta-merge (meta project) (meta profile)))) project - (reverse profiles))) + profiles)) (defn- lookup-profile "Lookup a profile in the given profiles map, warning when the profile doesn't diff --git a/leiningen-core/test/leiningen/core/test/project.clj b/leiningen-core/test/leiningen/core/test/project.clj index a09209af1..e1af7cf1b 100755 --- a/leiningen-core/test/leiningen/core/test/project.clj +++ b/leiningen-core/test/leiningen/core/test/project.clj @@ -70,19 +70,19 @@ (-> (make {:resource-paths ["resources"] :profiles {:blue {:resource-paths ["blue-resources"]}}}) - (merge-profiles [:qa :tes :blue]) + (merge-profiles [:blue :tes :qa]) :resource-paths))) (is (= ["/etc/myapp" "test/hi" "blue-resources"] (-> (make {:resource-paths ^:displace ["resources"] :profiles {:blue {:resource-paths ["blue-resources"]}}}) - (merge-profiles [:qa :tes :blue]) + (merge-profiles [:blue :tes :qa]) :resource-paths))) (is (= ["replaced"] (-> (make {:resource-paths ["resources"] :profiles {:blue {:resource-paths ^:replace ["replaced"]}}}) - (merge-profiles [:blue :qa :tes]) + (merge-profiles [:tes :qa :blue]) :resource-paths))) (is (= {:url "http://" :username "u" :password "p"} (-> (make @@ -169,13 +169,13 @@ (deftest test-merge-anon-profiles (is (= {:A 1, :C 3} (-> {:profiles {:a {:A 1} :b {:B 2}}} - (merge-profiles [:a {:C 3}]) + (merge-profiles [{:C 3} :a]) (dissoc :profiles))))) (deftest test-composite-profiles (is (= {:A '(1 3 2), :B 2, :C 3} - (-> {:profiles {:a [:c :b] - :b [:d {:A [1] :B 1 :C 1}] + (-> {:profiles {:a [:b :c] + :b [{:A [1] :B 1 :C 1} :d] :c {:A [2] :B 2} :d {:A [3] :C 3}}} (merge-profiles [:a]) @@ -186,7 +186,7 @@ (-> {:profiles {:a {:A 1 :B 2} :b {:B 2 :C 2} :c {:C 3} - :default [:c :b :a]}} + :default [:a :b :c]}} (merge-profiles [:default]) (dissoc :profiles)))))