From 85e77e68475cdf0ca1a0c6305652d55af0d60e1d Mon Sep 17 00:00:00 2001 From: Fernando Dobladez Date: Mon, 17 Jun 2013 13:13:30 -0300 Subject: [PATCH] Support for automatic activation of profiles via new `:active?` expression --- doc/PROFILES.md | 40 +++++++++++++++++++ leiningen-core/dev-resources/autoprofiles.clj | 24 +++++++++++ leiningen-core/src/leiningen/core/project.clj | 36 +++++++++++++---- .../test/leiningen/core/test/project.clj | 19 +++++++++ 4 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 leiningen-core/dev-resources/autoprofiles.clj diff --git a/doc/PROFILES.md b/doc/PROFILES.md index 48bda9780..38ceb9e37 100644 --- a/doc/PROFILES.md +++ b/doc/PROFILES.md @@ -109,6 +109,46 @@ Multiple profiles may be executed in series with colons: $ lein with-profile 1.3:1.4 test :database +You may also add/remove profiles from the active profile list by prefixing their +names with `+` and `-` respectively. Refer to `lein help with-profile` for more +details. + + +## Activating Profiles Automatically + +You might want to activate some profiles implicitly based on certain +conditions. In order to activate a profile implicitly, set the `:active?` key to a +boolean expression. + +In the following example, profile `:sticky` gets always activated: + +```clj +... + :profiles {:sticky {:active? true + ... } +``` + +In the following example, lein activates the `:linux` profile only when it is running on Linux: + +```clj +... + :profiles {:linux {:active? (= (System/getProperty "os.name") "Linux") + :dependencies [[org.blah/linux-specific-dep "1.1.0"]] } +``` + + +The value of keyword `:active?` may also be a predicate function receiving the +lein project as its only argument: + +```clj +... + :profiles {:non-gpl {:active? (fn [project] + (not (= (-> project :license :name) "GPL" ))) + :dependencies [[org.blah/impure-lib "2.0.0"]] } +``` + +You can override the implicit activation of a profile using `lein with-profile -profilename`. Refer to `lein help with-profile` for more details. + ## Composite Profiles Sometimes it is useful to define a profile as a combination of other diff --git a/leiningen-core/dev-resources/autoprofiles.clj b/leiningen-core/dev-resources/autoprofiles.clj new file mode 100644 index 000000000..2dacf9339 --- /dev/null +++ b/leiningen-core/dev-resources/autoprofiles.clj @@ -0,0 +1,24 @@ +(defproject autoprofiles "0.0.1" + :description "Test automatic activation of profiles via :active? expression" + + :profiles { + + :no_activation {:no_activation true } + + :literal_false {:active? false + :literal_false true } + + :literal_true {:active? true + :literal_true true } + + :expression_true {:active? (< 1 2) + :expression_true true } + + :expression_false {:active? (> 1 2) + :expression_false true } + + :fn_false {:active? (fn [project] (not (= (:name project) "autoprofiles" )) ) + :fn_false true } + + :fn_true {:active? (fn [project] (= (:name project) "autoprofiles" ) ) + :fn_true true } } ) diff --git a/leiningen-core/src/leiningen/core/project.clj b/leiningen-core/src/leiningen/core/project.clj index e82ce1c47..c95b8d4ec 100755 --- a/leiningen-core/src/leiningen/core/project.clj +++ b/leiningen-core/src/leiningen/core/project.clj @@ -396,12 +396,15 @@ right))) (defn- apply-profiles [project profiles] - (reduce (fn [project profile] - (with-meta - (meta-merge project profile) - (meta-merge (meta project) (meta profile)))) - project - profiles)) + + (let [transient-keys [:active?]] + + (reduce (fn [project profile] + (with-meta + (meta-merge project (apply dissoc profile transient-keys)) + (meta-merge (meta project) (meta profile)))) + project + profiles))) (defn- lookup-profile "Lookup a profile in the given profiles map, warning when the profile doesn't @@ -643,6 +646,22 @@ [:profiles] merge profiles-map)})) + +(defn- auto-active-profiles + "Given a project with :profiles metadata, return the keys of those profiles +for which the `:active?` expression or function `(fn [project])` evaluates to +true" + [project] + + (map key (filter (fn [[k profile]] + (let [activation (eval (:active? profile))] + (if (fn? activation) + (activation project) + activation))) + + (-> project meta :profiles)))) + + (defn read "Read project map out of file, which defaults to project.clj." ([file profiles] @@ -656,6 +675,9 @@ (throw (Exception. (format "%s must define project map" file)))) ;; return it to original state (ns-unmap 'leiningen.core.project 'project) - (init-profiles (project-with-profiles @project) profiles)))) + + (let [project (project-with-profiles @project)] + (init-profiles project (concat profiles (auto-active-profiles project))))))) + ([file] (read file [:default])) ([] (read "project.clj"))) diff --git a/leiningen-core/test/leiningen/core/test/project.clj b/leiningen-core/test/leiningen/core/test/project.clj index da5fde4b1..a47bad2c9 100755 --- a/leiningen-core/test/leiningen/core/test/project.clj +++ b/leiningen-core/test/leiningen/core/test/project.clj @@ -516,3 +516,22 @@ [["central" {:url "http://repo1.maven.org/maven2/" :snapshots false}] ["clojars" {:url "https://clojars.org/repo/"}]]}}))))))) + + + +(deftest test-read-auto-profiles + (let [actual (read (.getFile (io/resource "autoprofiles.clj")))] + + (testing "Profiles with truthy `:active?` expressions/functions must be active" + (is (:literal_true actual)) + (is (:expression_true actual)) + (is (:fn_true actual))) + + (testing "Profiles with falsy or missing `:active?` expressions must not be active" + (is (not (:no_activation actual))) + (is (not (:literal_false actual))) + (is (not (:expression_false actual))) + (is (not (:fn_false actual)))) + + (testing "The `:active?` entry must NOT leak out of the profiles and into the project" + (is (nil? (:active? actual))))))