leiningen/doc/PROFILES.md
Phil Hagelberg 798873d467 Merge pull request #1611 from techwhizbang/master
read-eval example provided in PROFILES.md
2014-07-24 13:25:04 -07:00

8 KiB

Profiles

You can change the configuration of your project by applying various profiles. For instance, you may want to have a few extra test data directories on the classpath during development without including them in the jar, or you may want to have development tools like Slamhound available in every project you hack on without modifying every single project.clj you use.

By default the :dev, :provided, :user, :system, and :base profiles are activated for each task, but their settings are not propagated downstream to projects that depend upon yours. Each profile is defined as a map which gets merged into your project map.

You can place any arbitrary key/value pairs supported by defproject into a given profile and they will be merged into the project map when that profile is activated.

The example below adds a "dummy-data" resources directory during development and a dependency upon "expectations" that's only used for tests.

(defproject myproject "0.5.0-SNAPSHOT"
  :description "A project for doing things."
  :dependencies [[org.clojure/clojure "1.4.0"]]
  :profiles {:dev {:resource-paths ["dummy-data"]
                   :dependencies [[expectations "1.4.41"]]}})

Declaring Profiles

In addition to project.clj, profiles also can be specified in profiles.clj within the project root. Profiles specified in profiles.clj will override profiles in project.clj, so this can be used for project-specific overrides that you don't want committed in version control.

User-wide profiles can also be specified in ~/.lein/profiles.clj. These will be available in all projects managed by Leiningen, though those profiles will be overridden by profiles of the same name specified in the project. System-wide profiles can be placed in /etc/leiningen/profiles.clj. They are treated the same as user profiles, but with lower precedence.

You can also define user-wide profiles within clj-files inside ~/.lein/profiles.d. The semantics within such files differ slightly from other profile files: rather than a map of maps, the profile map is the top-level within the file, and the name of the profile comes from the file itself (without the .clj part). Defining the same user-wide profile in both ~/.lein/profiles.clj and in ~/.lein/profiles.d is considered an error.

The :user profile is separate from :dev; the latter is intended to be specified in the project itself. In order to avoid collisions, the project should never define a :user profile, nor should a user-wide :dev profile be defined. Use the show-profiles task to list them.

If you want to access dependencies during development time for any project place them in your :user profile. Your ~/.lein/profiles.clj file could look something like this:

{:user {:plugins [[lein-pprint "1.1.1"]]
        :dependencies [[slamhound "1.3.1"]]}}

Merging

Profiles are merged by taking each key in the project map or profile map, combining the value if it's a collection and replacing it if it's not. Profiles specified later take precedence when replacing, just like the clojure.core/merge function. The dev profile takes precedence over user by default. Maps are merged recursively, sets are combined with clojure.set/union, and lists/vectors are concatenated. You can add hints via metadata that a given value should take precedence (:replace) or defer to values from a different profile (:displace) if you want to override this logic:

{:profiles {:dev {:prep-tasks ^:replace ["clean" "compile"]
                  :aliases ^:displace {"launch" "run"}}}}

The exception to this merge logic is that :plugins and :dependencies have custom de-duplication logic since they must be specified as vectors even though they behave like maps (because it only makes sense to have a single version of a given dependency present at once). The replace/displace metadata hints still apply though.

Remember that if a profile with the same name is specified in multiple files, the last one will replace the previous ones, no merging. (If you need to enable personal overrides of parts of a profile, you can use a composite profile with common and personal parts - something like :dev [:dev-common :dev-overrides]; you would then have just :dev-overrides {} in project.clj and override it in profiles.clj.)

Another use of profiles is to test against various sets of dependencies:

(defproject swank-clojure "1.5.0-SNAPSHOT"
  :description "Swank server connecting Clojure to Emacs SLIME"
  :dependencies [[org.clojure/clojure "1.2.1"]
                 [clj-stacktrace "0.2.4"]
                 [cdt "1.2.6.2"]]
  :profiles {:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
             :1.4 {:dependencies [[org.clojure/clojure "1.4.0-beta1"]]}})

Activating Profiles

To activate a different set of profiles for a given task, use the with-profile higher-order task:

$ lein with-profile 1.3 test :database

Multiple profiles may be combined with commas:

$ lein with-profile qa,user test :database

Multiple profiles may be executed in series with colons:

$ lein with-profile 1.3:1.4 test :database

The above invocations activate the given profiles in place of the defaults. To activate a profile in addition to the defaults, prepend it with a +:

$ lein with-profile +server run

You can also use - to deactivate a profile.

By default all profiles will share the same :target-path, which can cause problems if settings from one profile leak over into another. It's recommended to set :target-path to "target/%s", which will isolate each profile set and prevent anything from bleeding over.

Composite Profiles

Sometimes it is useful to define a profile as a combination of other profiles. To do this, just use a vector instead of a map as the profile value. This can be used to avoid duplication:

{:shared {:port 9229, :protocol "https"}
 :qa [:shared {:servers ["qa.mycorp.com"]}]
 :stage [:shared {:servers ["stage.mycorp.com"]}]
 :production [:shared {:servers ["prod1.mycorp.com", "prod1.mycorp.com"]}]}

Composite profiles are used by Leiningen internally for the :default profile, which is the profile used if you don't change it using with-profile. The :default profile is defined to be a composite of [:base :system :user :provided :dev], but you can change this in your project.clj just like any other profile.

Using Functions

Often you want to read an environment variable or execute a function to capture a value to use in your profiles. In order to do such a thing with the profiles.clj you'll need to use the read-eval syntax.

Here is an example of such a case:

{:user {:compile-path  #=(eval (System/getenv "ci.compile-path")),
        :target-path #=(eval (System/getenv "ci.target-path"))}}

Debugging

To see how a given profile affects your project map, use the lein-pprint plugin:

$ lein with-profile 1.4 pprint
{:compile-path "/home/phil/src/leiningen/lein-pprint/classes",
 :group "lein-pprint",
 :source-path ("/home/phil/src/leiningen/lein-pprint/src"),
 :dependencies
 ([org.clojure/tools.nrepl "0.0.5" :exclusions [org.clojure/clojure]]
  [clojure-complete "0.1.4" :exclusions [org.clojure/clojure]]
  [org.thnetos/cd-client "0.3.3" :exclusions [org.clojure/clojure]]),
 :target-path "/home/phil/src/leiningen/lein-pprint/target",
 :name "lein-pprint",
 [...]
 :description "Pretty-print a representation of the project map."}

In order to prevent profile settings from being propagated to other projects that depend upon yours, the :default profiles are removed from your project when generating the pom, jar, and uberjar, and an :uberjar profile, if present, is included when creating uberjars. (This can be useful if you want to specify a :main namespace for uberjar use without triggering AOT during regular development.) Profiles activated through an explicit with-profile invocation will be preserved.