diff --git a/.gitignore b/.gitignore index 910e3722e..9d2e3fcd1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ autodoc/ bin/nom .lein-failures /lein.man +/scratch.clj +.lein-deps-sum \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..f11f961d0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +--- +script: bin/lein self-install; bin/lein test +branches: + only: + - 1.x + - master +notifications: + irc: "irc.freenode.org#leiningen" \ No newline at end of file diff --git a/NEWS b/NEWS index 76d9090cc..120923d35 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,47 @@ Leiningen NEWS -- history of user-visible changes += 1.6.2 / 2011-11-11 + +* Let run task work with main functions from Java classes. + +* Fix bug where exceptions would break interactive task. + +* Default to Clojure 1.3.0 for new projects. + +* Allow Leiningen home to exist inside project directory. (Heinz N. Gies) + +* Remove old versions of plugins when upgrading. + +* Add user-level :deploy-repositories list. (Michał Marczyk) + +* Fix a bug where class files from proxy objects weren't considered + part of the project. (Stephen Compall) + +* Make deps cause implicit clean to avoid AOT version mismatches. + +* Include Java source files in jar. (Nathan Marz) + +* Add separate :deploy-repositories list. (Chas Emerick) + +* Maintain order in repositories list. (Colin Jones) + +* Fix a bug where :omit-default-repos wouldn't skip Maven Central. (Chas Emerick) + +* Make deps extract native dependencies for all architectures, not just current. + +* Fix page count on search results. + +* Fix a bug where "lein plugin install" could skip dependencies. + +* Reimplement eval-in-project to use clojure.java.shell instead of Ant. + +* Separate LEIN_JVM_OPTS from JVM_OPTS. + += 1.6.1.1 / 2011-09-06 + +* Turn off workaround for Clojure's agent thread pool keeping the JVM alive + by default. Use :shutdown-agents in project.clj to enable it. + = 1.6.1 / 2011-07-06 * Allow alternate main namespace to be used during uberjar creation. diff --git a/README.md b/README.md index 21f57cdb3..67b14a14e 100644 --- a/README.md +++ b/README.md @@ -15,28 +15,21 @@ exercise in frustration. With Leiningen, you just write Clojure. ## Installation -Leiningen bootstraps itself using the lein shell script; -there is no separate 'install script'. It installs its dependencies +Leiningen bootstraps itself using the `lein` shell script; +there is no separate install script. It installs its dependencies upon the first run on unix, so the first run will take longer. -1. [Download the script](https://github.com/technomancy/leiningen/raw/stable/bin/lein). -2. Place it on your path and chmod it to be executable. - -I like to place it in ~/bin, but it can go anywhere on the $PATH. - -On Windows most users can - -1. Download the Windows distribution -[leiningen-1.5.2-win.zip](https://github.com/downloads/technomancy/leiningen/leiningen-1.5.2-win.zip) -2. Unzip in a folder of choice. -3. Include the "lein" directory in PATH. +1. [Download the script](https://raw.github.com/technomancy/leiningen/stable/bin/lein). +2. Place it on your path. (I like to use `~/bin`) +3. Set it to be executable. (`chmod 755 ~/bin/lein`) +On Windows most users can get +[the batch file](https://raw.github.com/technomancy/leiningen/stable/bin/lein.bat). If you have wget.exe or curl.exe already installed and in PATH, you -can download either [the stable version -lein.bat](https://github.com/technomancy/leiningen/raw/stable/bin/lein.bat), -or [the development -version](https://github.com/technomancy/leiningen/raw/master/bin/lein.bat) -and use self-install. +can just run `lein self-install`, otherwise get the standalone jar from the +[downloads page](https://github.com/technomancy/leiningen/downloads). +If you have [Cygwin](http://www.cygwin.com/) you should be able to use +the shell script above rather than the batch file. ## Usage @@ -55,48 +48,59 @@ project, but here are the commonly-used tasks: $ lein install [NAME VERSION] # install a project -Use lein help to see a complete list. lein help -$TASK shows the usage for a specific one. + $ lein search ... # find recent jars for your project.clj dependencies + +Use `lein help` to see a complete list. `lein help $TASK` shows the +usage for a specific one. You can also chain tasks together in a single command by using commas: $ lein clean, test foo.test-core, jar Most tasks need to be run from somewhere inside a project directory to -work, but some (new, help, version, -plugin, and the two-argument version of install) may -run from anywhere. +work, but some (`new`, `help`, `version`, `plugin`, and the +two-argument version of `install`) may run from anywhere. -The install task places shell scripts in the ~/.lein/bin +The install task places shell scripts in the `~/.lein/bin` directory for projects that include them, so if you want to take -advantage of this, you should put it on your $PATH. +advantage of this, you should put it on your `$PATH`. ## Configuration -The project.clj file in the project root should look like this: +The `project.clj` file in the project root should look like this: + +```clojure +(defproject myproject "0.5.0-SNAPSHOT" + :description "A project for doing things." + :url "http://github.com/technomancy/myproject" + :dependencies [[org.clojure/clojure "1.2.1"] + [org.clojure/clojure-contrib "1.2.0"]] + :dev-dependencies [[lein-ring "0.4.5"]]) +``` - (defproject myproject "0.5.0-SNAPSHOT" - :description "A project for doing things." - :url "http://github.com/technomancy/myproject" - :dependencies [[org.clojure/clojure "1.2.1"] - [org.clojure/clojure-contrib "1.2.0"]] - :dev-dependencies [[lein-ring "0.4.5"]]) +If you're looking for the most recent jar of one of your dependencies, +use `lein search`. -The lein new task generates a project skeleton with an +The `lein new` task generates a project skeleton with an appropriate starting point from which you can work. See the [sample.project.clj](https://github.com/technomancy/leiningen/blob/stable/sample.project.clj) file for a detailed listing of configuration options. You can also have user-level configuration that applies for all -projects. The ~/.lein/init.clj file will be loaded every time +projects. The `~/.lein/init.clj` file will be loaded every time Leiningen launches; any arbitrary code may go there. This code is executed inside Leiningen itself, not in your project. Set the -:repl-init key in project.clj to point to a namespace if +`:repl-init` key in project.clj to point to a namespace if you want code executed inside your project. -You can also manage your plugins with the plugin task. Use -the same arguments you would put in the Leiningen :dev-dependencies if -you were only using the plugin on a single project. +## Leiningen Plugins + +Leiningen supports plugins. See [the plugins wiki +page](https://github.com/technomancy/leiningen/wiki/Plugins) for a +full list. If a plugin is needed for successful test or build runs, +(such as lein-tar) then it should be added to `:dev-dependencies` in +project.clj, but if it's for your own convenience (such as +swank-clojure) then it should be added using the `plugin` task: $ lein plugin install lein-clojars "0.6.0" @@ -156,7 +160,7 @@ See the plugin task's help for more information. **Q:** I want to hack two projects in parallel, but it's annoying to switch between them. **A:** Use a feature called _checkout dependencies_. If you create a - directory called checkouts in your project root and symlink + directory called `checkouts` in your project root and symlink some other project roots into it, Leiningen will allow you to hack on them in parallel. That means changes in the dependency will be visible in the main project without having to go through the whole @@ -174,7 +178,7 @@ See the plugin task's help for more information. **Q:** What does java.lang.NoSuchMethodError: clojure.lang.RestFn.(I)V mean? **A:** It means you have some code that was AOT (ahead-of-time) compiled with a different version of Clojure than the one you're - currently using. If it persists after running lein clean then it + currently using. If it persists after running `lein clean` then it is a problem with your dependencies. Note that for your own project that AOT compilation in Clojure is much less important than it is in other languages. There are a few @@ -185,12 +189,12 @@ See the plugin task's help for more information. **Q:** I'm behind an HTTP proxy; how can I fetch my dependencies? **A:** Currently you need to configure the underlying Maven library by - creating ~/.m2/settings.xml as explained in the + creating `~/.m2/settings.xml` as explained in the [Maven guide](http://maven.apache.org/guides/mini/guide-proxies.html). **Q:** What can be done to speed up launch? **A:** The main delay involved in Leiningen comes from starting the - JVM. Launching "lein interactive" will give you an interactive + JVM. Launching `lein interactive` will give you an interactive session so you can run many tasks against the same process instead of launching a new one every time. Depending on your editor you may also be able to take advantage of its Clojure integration. (See @@ -204,15 +208,14 @@ See the plugin task's help for more information. launch a client JVM, but this only works on 32-bit Hotspot. If you are on a 64-bit machine you can still use a client JVM if you install 32-bit packages; on Debian try ia32-sun-java6-bin. Once - you've installed it, run sudo update-java-alternatives -s - ia32-java-6-sun. + you've installed it, run `sudo update-java-alternatives -s ia32-java-6-sun`. **Q:** I don't have access to stdin inside my project. **A:** There's a bug in the Ant library that Leiningen uses to spawn new processes that blocks access to console input. This means that - functions like read-line will not work as expected in most - contexts, though the repl task necessarily includes a - workaround. You can also use the trampoline task to + functions like `read-line` will not work as expected in most + contexts, though the `repl` task necessarily includes a + workaround. You can also use the `trampoline` task to launch your project's JVM after Leiningen's has exited rather than launching it as a subprocess @@ -244,15 +247,15 @@ mailing list and mailing a SASE. You don't need to "build" Leiningen per se, but when you're using a checkout you will need to get its dependencies in place. In most cases -a lein self-install will usually get you what you +a `lein self-install` will usually get you what you need. However, this will occasionally fail for very new SNAPSHOT versions since the standalone jar will not have been uploaded yet. Alternatively if you have a copy of an older Leiningen version around (at least 1.1.0, installed as lein-stable, for example), then you can -run "lein-stable deps" in your checkout. If Leiningen's dependencies +run `lein-stable deps` in your checkout. If Leiningen's dependencies change it will be necessary to remove the lib/ directory entirely -before running "lein deps" again. (This is not necessary for most +before running `lein deps` again. (This is not necessary for most projects, but Leiningen has unique bootstrapping issues when working on itself.) @@ -261,7 +264,7 @@ You can also use Maven, just for variety's sake: $ mvn dependency:copy-dependencies $ mv target/dependency lib -Symlink bin/lein from your checkout into a location on the $PATH. The +Symlink `bin/lein` from your checkout into a location on the $PATH. The script can figure out when it's being called from inside a checkout and use the checkout rather than the self-install uberjar if necessary. diff --git a/bin/lein b/bin/lein index 93a255ce3..f0eff7354 100755 --- a/bin/lein +++ b/bin/lein @@ -33,7 +33,12 @@ do done if [ "$LEIN_HOME" = "" ]; then + if [ -d "$PWD/.lein" ] && [ "$PWD" != "$HOME" ]; then + echo "Leiningen is running in bundled mode." + LEIN_HOME="$PWD/.lein" + else LEIN_HOME="$HOME/.lein" + fi fi DEV_PLUGINS="$(ls -1 lib/dev/*jar 2> /dev/null)" @@ -67,8 +72,8 @@ unique_user_plugins () { LEIN_PLUGIN_PATH="$(echo "$DEV_PLUGINS" | tr \\n :)" LEIN_USER_PLUGIN_PATH="$(echo "$(unique_user_plugins)" | tr \\n :)" -CLASSPATH="$CLASSPATH:$LEIN_PLUGIN_PATH:$LEIN_USER_PLUGIN_PATH:test/:src/" -LEIN_JAR="$HOME/.lein/self-installs/leiningen-$LEIN_VERSION-standalone.jar" +CLASSPATH="$CLASSPATH:$LEIN_PLUGIN_PATH:$LEIN_USER_PLUGIN_PATH:test/:src/:resources/" +LEIN_JAR="$LEIN_HOME/self-installs/leiningen-$LEIN_VERSION-standalone.jar" CLOJURE_JAR="$HOME/.m2/repository/org/clojure/clojure/1.2.1/clojure-1.2.1.jar" NULL_DEVICE=/dev/null @@ -133,6 +138,11 @@ export JVM_OPTS=${JVM_OPTS:-"$JAVA_OPTS"} # If you're packaging this for a package manager (.deb, homebrew, etc) # you need to remove the self-install and upgrade functionality. if [ "$1" = "self-install" ]; then + if [ -r "$LEIN_JAR" ]; then + echo "The self-install jar already exists at $LEIN_JAR." + echo "If you wish to re-download, delete it and rerun \"$0 self-install\"." + exit 1 + fi echo "Downloading Leiningen now..." LEIN_DIR=`dirname "$LEIN_JAR"` mkdir -p "$LEIN_DIR" @@ -174,7 +184,7 @@ elif [ "$1" = "upgrade" ]; then $HTTP_CLIENT "$TARGET" "$LEIN_SCRIPT_URL" \ && mv "$TARGET" "$SCRIPT" \ && chmod +x "$SCRIPT" \ - && echo && $SCRIPT self-install && echo && echo "Now running" `$SCRIPT version` + && echo && "$SCRIPT" self-install && echo && echo "Now running" `$SCRIPT version` exit $?;; *) echo "Aborted." @@ -215,7 +225,7 @@ else # Test to see if rlwrap supports custom quote chars rlwrap -m -q '"' echo "hi" > /dev/null 2>&1 if [ $? -eq 0 ]; then - RLWRAP="$RLWRAP -m -q '\"'" + RLWRAP="$RLWRAP -r -m -q '\"'" fi fi fi diff --git a/bin/lein-pkg b/bin/lein-pkg old mode 100755 new mode 100644 index 2fcda0ea8..2263854bf --- a/bin/lein-pkg +++ b/bin/lein-pkg @@ -4,7 +4,7 @@ # It has all the cross-platform stuff stripped out as well as the # logic for running from checkouts and self-upgrading. -LEIN_VERSION="1.6.1" +LEIN_VERSION="1.6.2" export LEIN_VERSION if [ `whoami` = "root" ] && [ "$LEIN_ROOT" = "" ]; then @@ -28,7 +28,15 @@ done # Support $JAVA_OPTS for backwards-compatibility. JVM_OPTS=${JVM_OPTS:-"$JAVA_OPTS"} JAVA_CMD=${JAVA_CMD:-"java"} -LEIN_HOME=${LEIN_HOME:-"$HOME/.lein"} + +if [ "$LEIN_HOME" = "" ]; then + if [ -d "$PWD/.lein" ] && [ "$PWD" != "$HOME" ]; then + echo "Leiningen is running in bundled mode." + LEIN_HOME="$PWD/.lein" + else + LEIN_HOME="$HOME/.lein" + fi +fi DEV_PLUGINS="$(ls -1 lib/dev/*jar 2> /dev/null)" USER_PLUGINS="$(ls -1 "$LEIN_HOME"/plugins/*jar 2> /dev/null)" @@ -61,24 +69,16 @@ unique_user_plugins () { LEIN_PLUGIN_PATH="$(echo "$DEV_PLUGINS" | tr \\n :)" LEIN_USER_PLUGIN_PATH="$(echo "$(unique_user_plugins)" | tr \\n :)" -CLASSPATH="$CLASSPATH:$LEIN_PLUGIN_PATH:$LEIN_USER_PLUGIN_PATH:test/:src/" -LEIN_JAR="$HOME/.lein/self-installs/leiningen-$LEIN_VERSION-standalone.jar" -CLOJURE_JAR="/usr/share/java/clojure-1.2.jar:/usr/share/java/asm3.jar" +CLASSPATH="$CLASSPATH:$LEIN_PLUGIN_PATH:$LEIN_USER_PLUGIN_PATH:test/:src/:resources/" +CLOJURE_JAR="/usr/share/java/clojure-1.2.jar:/usr/share/java/asm3.jar:/usr/share/java/asm3-commons.jar" NULL_DEVICE=/dev/null # apply context specific CLASSPATH entries -if [ -f .classpath ]; then - CLASSPATH="`cat .classpath`:$CLASSPATH" -fi - -# normalize $0 on certain BSDs -if [ "$(dirname "$0")" = "." ]; then - SCRIPT="$(which $(basename "$0"))" -else - SCRIPT="$0" +if [ -f .lein-classpath ]; then + CLASSPATH="`cat .lein-classpath`:$CLASSPATH" fi -SHARE_JARS="ant ant-launcher classworlds clojure-1.2 \ +SHARE_JARS="ant ant-launcher classworlds clojure-1.2 clojure-contrib \ lucene-memory maven-ant-tasks maven-artifact maven-artifact-manager \ maven-error-diagnostics maven-model maven-settings maven-project maven-profile \ maven-repository-metadata plexus-container-default-alpha plexus-interpolation \ @@ -89,8 +89,10 @@ for JAR in $SHARE_JARS; do CLASSPATH="$CLASSPATH":"/usr/share/java/$JAR.jar" done -# Keep already-packaged Leiningen jar off the classpath during packaging. -if [ ! -r src/leiningen/core.clj ]; then +# Do not use installed leiningen jar during self-compilation +if ! { [ "$1" = "compile" ] && + grep -qsE 'defproject leiningen[[:space:]]+"[[:digit:].]+"' \ + project.clj ;}; then CLASSPATH="$CLASSPATH":/usr/share/java/leiningen-$LEIN_VERSION.jar fi @@ -104,13 +106,13 @@ if ([ "$1" = "repl" ] || [ "$1" = "interactive" ] || [ "$1" = "int" ]) && [ -z $INSIDE_EMACS ] && [ "$TERM" != "dumb" ]; then which rlwrap > /dev/null if [ $? -eq 0 ]; then - RLWRAP="rlwrap -m -q '\"'" # custom quote chars + RLWRAP="rlwrap -r -m -q '\"'" # custom quote chars fi fi if [ "$1" = "trampoline" ]; then TRAMPOLINE_FILE="/tmp/lein-trampoline-$$" - $JAVA_CMD -Xbootclasspath/a:"$CLOJURE_JAR" -client $JVM_OPTS \ + $JAVA_CMD -Xbootclasspath/a:"$CLOJURE_JAR" -client $LEIN_JVM_OPTS \ -Dleiningen.original.pwd="$ORIGINAL_PWD" \ -Dleiningen.trampoline-file=$TRAMPOLINE_FILE -cp "$CLASSPATH" \ clojure.main -e "(use 'leiningen.core)(-main)" \ @@ -118,11 +120,11 @@ if [ "$1" = "trampoline" ]; then if [ -r $TRAMPOLINE_FILE ]; then TRAMPOLINE="$(cat $TRAMPOLINE_FILE)" rm $TRAMPOLINE_FILE - exec sh -c "TRAMPOLINE" + exec sh -c "$TRAMPOLINE" fi else - exec $RLWRAP $JAVA_CMD -Xbootclasspath/a:"$CLOJURE_JAR" -client $JVM_OPTS \ - -Dleiningen.original.pwd="$ORIGINAL_PWD" \ + exec $RLWRAP $JAVA_CMD -Xbootclasspath/a:"$CLOJURE_JAR" -client \ + $LEIN_JVM_OPTS -Dleiningen.original.pwd="$ORIGINAL_PWD" \ -cp "$CLASSPATH" clojure.main -e "(use 'leiningen.core)(-main)" \ $NULL_DEVICE "$@" fi diff --git a/bin/lein.bat b/bin/lein.bat index 02e1f425b..c819373e7 100644 --- a/bin/lein.bat +++ b/bin/lein.bat @@ -10,14 +10,6 @@ if "%LEIN_VERSION:~-9%" == "-SNAPSHOT" ( set SNAPSHOT=NO ) -:: LEIN_JAR and LEIN_HOME variables can be set manually. - -if "x%LEIN_HOME%" == "x" set LEIN_HOME=%USERPROFILE%\.lein -if "x%LEIN_JAR%" == "x" set LEIN_JAR="!LEIN_HOME!\self-installs\leiningen-!LEIN_VERSION!-standalone.jar" - -if "%1" == "self-install" goto SELF_INSTALL -if "%1" == "upgrade" goto NO_UPGRADE - set ORIGINAL_PWD=%CD% :: If ORIGINAL_PWD ends with a backslash (such as C:\), :: we need to escape it with a second backslash. @@ -26,6 +18,21 @@ if "%ORIGINAL_PWD:~-1%x" == "\x" set "ORIGINAL_PWD=%ORIGINAL_PWD%\" call :FIND_DIR_CONTAINING_UPWARDS project.clj if "%DIR_CONTAINING%" neq "" cd "%DIR_CONTAINING%" +:: LEIN_JAR and LEIN_HOME variables can be set manually. + +if "x%LEIN_HOME%" == "x" ( + if exist "%CD%\.lein" ( + if /I NOT %CD%==%USERPROFILE% echo Running in bundled mode. + set LEIN_HOME=%CD%\.lein + ) else ( + set LEIN_HOME=%USERPROFILE%\.lein + ) +) + +if "x%LEIN_JAR%" == "x" set LEIN_JAR="!LEIN_HOME!\self-installs\leiningen-!LEIN_VERSION!-standalone.jar" + +if "%1" == "self-install" goto SELF_INSTALL +if "%1" == "upgrade" goto NO_UPGRADE set DEV_PLUGINS=" for %%j in (".\lib\dev\*.jar") do ( @@ -34,7 +41,7 @@ for %%j in (".\lib\dev\*.jar") do ( set DEV_PLUGINS=!DEV_PLUGINS!" call :BUILD_UNIQUE_USER_PLUGINS -set CLASSPATH="%CLASSPATH%";%DEV_PLUGINS%;%UNIQUE_USER_PLUGINS%;test;src +set CLASSPATH="%CLASSPATH%";%DEV_PLUGINS%;%UNIQUE_USER_PLUGINS%;test;src;resources :: Apply context specific CLASSPATH entries set CONTEXT_CP= diff --git a/doc/DEPLOY.md b/doc/DEPLOY.md index 7cd9d2816..2acb7495b 100644 --- a/doc/DEPLOY.md +++ b/doc/DEPLOY.md @@ -44,11 +44,26 @@ The private server will need to be added to the :repositories listing in project.clj. Archiva and Nexus offer separate repositories for snapshots and releases, so you'll want two entries for them: - :repositories {"snapshots" {:url "http://blueant.com/archiva/snapshots" - :username "milgrim" :password "locative.1"} - "releases" "http://blueant.com/archiva/internal"} +```clj +:repositories {"snapshots" {:url "http://blueant.com/archiva/snapshots" + :username "milgrim" :password "locative.1"} + "releases" "http://blueant.com/archiva/internal"} +``` -Private repositories need authentication credentials. You'll need to +If you are are deploying to a repository that is _only_ used for deployment +and never for dependency resolution, then it should be specified in a +`:deploy-repositories` slot instead of included in the more general-purpose +`:repositories` map; the former is checked by `lein deploy` before the latter. +Deployment-only repositories useful across a number of locally developed +projects may also be specified in the `settings` map in `~/.lein/init.clj`: + +```clj +(def settings {:deploy-repositories { ... }}) +``` + +### Authentication + +Private repositories often need authentication credentials. You'll need to provide either a :username/:password combination or a :private-key location with or without a :passphrase. If you want to avoid putting sensitive @@ -57,12 +72,16 @@ entry above, you can store authentication information in ~/.lein/init.clj as a leiningen-auth map keyed off the repository's URL: - (def leiningen-auth {"http://localhost:8080/archiva/repository/internal/" - {:username "milgrim" :password "locative.2"}}) +```clj +(def leiningen-auth {"http://localhost:8080/archiva/repository/internal/" + {:username "milgrim" :password "locative.2"}}) +``` This also allows different users using the same checkout to upload using different credentials. +### Deployment + Once you've set up a private repository and configured project.clj appropriately, you can deploy to it: @@ -72,4 +91,5 @@ If the project's current version is a SNAPSHOT, it will deploy to the snapshots repository; otherwise it will go to releases. The deploy task also takes a repository name as an argument that will be looked up in the -:repositories map if you want to override this. +:deploy-repositories and :repositories maps +if you want to override this. diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index d7d3536f6..5184b693f 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -29,24 +29,11 @@ a project subprocess. Some tasks may only be run in the context of a project. For tasks like this, name the first argument project. Leiningen will inspect -the argument list and pass in the current project if needed. - -Some tasks can be run inside a project or outside, but would benefit -from having the project argument if they're run from a project. For -these, name the first argument something like project-or-foo, -and it will be passed the project argument when appropriate. - -The project is a map which is based on the project.clj file, but it -also has :name, :group, :version, and :root keys added in. If you want -it to take parameters from the command-line invocation, you can make -the function take more arguments. - -Note that Leiningen is an implied dependency of all plugins; you -should not explicitly list it in the project.clj file. You also don't -need to list Clojure, but you will be locked into using the same -version of Clojure that Leiningen is using. For Leiningen 1.x, a -dependency on Clojure Contrib is also implied, though this is gone in -2.0. +the argument list and pass in the current project if needed. The +project is a map which is based on the project.clj file, but it also +has :name, :group, :version, and :root keys added in. If you want it +to take parameters from the command-line invocation, you can make the +function take more arguments. The "lein help" task will display the first line of the task function's docstring as a summary. Then "lein help $TASK" will use @@ -119,13 +106,15 @@ altering the return value, only running the function conditionally, etc. The add-hook function takes a var of the task it's meant to apply to and a function to perform the wrapping: - (use 'robert.hooke) +```clj +(use 'robert.hooke) - (defn skip-integration-hook [task & args] - (binding [clojure.test/test-var (test-var-skip :integration)] - (apply task args))) +(defn skip-integration-hook [task & args] + (binding [clojure.test/test-var (test-var-skip :integration)] + (apply task args))) - (add-hook #'leiningen.test/test skip-integration-hook) +(add-hook #'leiningen.test/test skip-integration-hook) +``` Hooks compose, so be aware that your hook may be running inside another hook. To take advantage of your hooks functionality, projects @@ -151,6 +140,26 @@ itself, add a '.lein-classpath' file a project's root. Its contents will be prepended to Leiningen's classpath when Leiningen is invoked upon that project. +## Clojure Version + +Note that Leiningen is an implied dependency of all plugins; you don't +need to explicitly list it in the project.clj file. You also don't +need to list Clojure or Contrib, but you will be locked into using the +same version of Clojure that Leiningen is using. + +Versions of Leiningen prior to 1.2.0 used Clojure 1.1, while the rest +of the 1.x line uses Clojure 1.2. Leiningen 2.0 will use Clojure 1.3. +If you need to use a different version of Clojure from within a +Leiningen plugin, you can use `eval-in-project` with a dummy project +argument: + +```clj +(eval-in-project {:local-repo-classpath true + :dependencies '[[org.clojure/clojure "1.3.0"]] + :native-path "/tmp" :root "/tmp" :compile-path "/tmp"} + '(println "hello from" *clojure-version*)) +``` + ## Lancet If your plugins need to do a fair amount of filesystem-y things, you diff --git a/doc/TUTORIAL.md b/doc/TUTORIAL.md index 43d9622c8..2f7710df9 100644 --- a/doc/TUTORIAL.md +++ b/doc/TUTORIAL.md @@ -59,9 +59,11 @@ repositories for you. $ cat project.clj - (defproject myproject "1.0.0-SNAPSHOT" - :description "FIXME: write description" - :dependencies [[org.clojure/clojure "1.2.1"]]) +```clj +(defproject myproject "1.0.0-SNAPSHOT" + :description "FIXME: write description" + :dependencies [[org.clojure/clojure "1.2.1"]]) +``` Fill in the :description with a short paragraph so that your project will show up in search results once you publish it. At some point @@ -140,8 +142,7 @@ instead of a single version: [org.clojure/clojure "[1.1,1.2]"] ; <= will match 1.1.0 through 1.2.0. -See [Maven's version range -specification](http://maven.apache.org/plugins/maven-enforcer-plugin/rules/versionRanges.html) +See [Maven's version range specification](http://j.mp/twc713) for details. Don't do this unless you have manually confirmed that it works with each of those versions though. You can't assume that your dependencies will use semantic versions; some projects even introduce @@ -206,14 +207,18 @@ Rather than running your whole suite or just a few namespaces at a time, you can run a subset of your tests using test selectors. To do this, you attach metadata to various deftests. - (deftest ^{:integration true} network-heavy-test - (is (= [1 2 3] (:numbers (network-operation))))) +```clj +(deftest ^{:integration true} network-heavy-test + (is (= [1 2 3] (:numbers (network-operation))))) +``` Then add a :test-selectors map to project.clj: - :test-selectors {:default (fn [v] (not (:integration v))) - :integration :integration - :all (fn [_] true)} +```clj +:test-selectors {:default (fn [v] (not (:integration v))) + :integration :integration + :all (fn [_] true)} +``` Now if you run "lein test" it will only run deftests that don't have :integration metadata, while "lein test :integration" will only run @@ -298,12 +303,14 @@ nontechnical users. For this to work you'll need to specify a namespace as your :main in project.clj. By this point our project.clj file should look like this: - (defproject myproject "1.0.0-SNAPSHOT" - :description "This project is MINE." - :dependencies [[org.clojure/clojure "1.2.0"] - [org.apache.lucene/lucene-core "3.0.2"] - [lancet "1.0.0"]] - :main myproject.core) +```clj +(defproject myproject "1.0.0-SNAPSHOT" + :description "This project is MINE." + :dependencies [[org.clojure/clojure "1.2.0"] + [org.apache.lucene/lucene-core "3.0.2"] + [lancet "1.0.0"]] + :main myproject.core) +``` The namespace you specify will need to contain a -main function that will get called when your standalone jar is run. This @@ -312,11 +319,13 @@ namespace should have a (:gen-class) declaration in the passed the command-line arguments. Let's try something simple in src/myproject/core.clj: - (ns myproject.core - (:gen-class)) +```clj +(ns myproject.core + (:gen-class)) - (defn -main [& args] - (println "Welcome to my project! These are your args:" args)) +(defn -main [& args] + (println "Welcome to my project! These are your args:" args)) +``` Now we're ready to generate your uberjar: @@ -367,8 +376,10 @@ project.clj, Leiningen automatically generates a simple shell script wrapper when you create your jar file. However, if you need more control you can provide a map instead: +```clj :shell-wrapper {:main myproject.core :bin "bin/myproject"} +``` Normally the shell wrapper will invoke the -main function in your project's :main namespace, but specifying this option triggers AOT for diff --git a/doc/lein.1 b/doc/lein.1 index 5be6ad481..9b9f075da 100644 --- a/doc/lein.1 +++ b/doc/lein.1 @@ -1,4 +1,4 @@ -./"to render: groff -Tascii -man doc/lein.1 > lein.man" +.\"to render: groff -Tascii -man doc/lein.1 > lein.man" .TH LEININGEN 1 "2011 June 30" .SH NAME lein \- Automate Clojure projects diff --git a/pom.xml b/pom.xml index 2272471d7..673cf26f2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 leiningen leiningen @@ -82,7 +83,7 @@ org.apache.maven maven-ant-tasks - 2.0.10 + 2.1.3 ant diff --git a/project.clj b/project.clj index cf7d49e3c..bf5f7cf05 100644 --- a/project.clj +++ b/project.clj @@ -12,6 +12,6 @@ [lancet "1.0.1"] [jline "0.9.94" :exclusions [junit]] [robert/hooke "1.1.2"] - [org.apache.maven/maven-ant-tasks "2.0.10" :exclusions [ant]]] + [org.apache.maven/maven-ant-tasks "2.1.3" :exclusions [ant]]] :disable-implicit-clean true :eval-in-leiningen true) diff --git a/sample.project.clj b/sample.project.clj index 2d1a4bdf1..bfe9d074d 100644 --- a/sample.project.clj +++ b/sample.project.clj @@ -116,9 +116,9 @@ :repl-retry-limit 1000 ;; Emit warnings on all reflection calls. :warn-on-reflection true - ;; Set this in order to only use the :repositories you list below. Note that - ;; a bug in maven-ant-tasks prevents Leiningen from excluding Maven Central, - ;; so in effect this simply omits Clojars. + ;; Set this in order to only use the :repositories you list below. Note that, + ;; if any artifacts are not found, Maven Central will still be reported to + ;; have been checked, even though it was not. :omit-default-repositories true :repositories {"java.net" "http://download.java.net/maven/2" "sonatype" @@ -138,9 +138,16 @@ :username "milgrim" :password "locative.1"} "releases" {:url "http://blueant.com/archiva/internal" :username "milgrim" :password "locative.1"}} + ;; the deploy task will give preference to repositories specified in + ;; :deploy-repositories, and repos listed there will not be used for + ;; dependency resolution + :deploy-repositories {"releases" {:url "http://blueant.com/archiva/internal/releases" + :username "milgrim" :password "locative.1"} + "snapshots" "http://blueant.com/archiva/internal/snapshots"} ;; If you'd rather use a different directory structure, you can set these. :source-path "src/main/clojure" - :library-path "target/dependency" + :compile-path "target/classes" ; for .class files + :library-path "target/dependency" ; for .jar files :test-path "src/test/clojure" :resources-path "src/main/resource" ; non-code files included in classpath/jar :dev-resources-path "src/test/resource" ; added to dev classpath but not jar @@ -166,7 +173,16 @@ ;; You can set JVM-level options here. :jvm-opts ["-Xmx1g"] ;; If your project is a Leiningen plugin, set this to skip the subprocess step - :eval-in-leiningen false) + :eval-in-leiningen false + ;; Leiningen includes a workaround for a problem with Clojure's + ;; agent thread pool. If you see RejectedExecutionException using + ;; futures or agents, you may be working with a plugin that doesn't + ;; take this workaround into account yet--see the "Threads" section + ;; of doc/PLUGINS.md. This key will disable Leiningen's workaround. + ;; It may cause some other plugins to fail to exit when they finish. + :skip-shutdown-agents true + ;; Set parent for working with in a multi-module maven project + :parent [org.example/parent "0.0.1" :relative-path "../parent/pom.xml"]) ;; You can use Robert Hooke to modify behaviour of any task function, ;; but the prepend-tasks function is shorthand that is more convenient diff --git a/src/leiningen/classpath.clj b/src/leiningen/classpath.clj index 5d902d06f..a404a6bbe 100644 --- a/src/leiningen/classpath.clj +++ b/src/leiningen/classpath.clj @@ -1,11 +1,10 @@ (ns leiningen.classpath "Print the classpath of the current project." (:use [leiningen.core :only [read-project no-dev?]] - [leiningen.deps :only [find-jars]] + [leiningen.deps :only [find-deps-files]] [leiningen.util.paths :only [leiningen-home]] [clojure.java.io :only [file]] - [clojure.string :only [join]]) - (:import (org.apache.tools.ant.types Path))) + [clojure.string :only [join]])) (defn- read-dependency-project [dep] (let [project (.getAbsolutePath (file dep "project.clj"))] @@ -35,15 +34,6 @@ :when (re-find #"\.jar$" (.getName jar))] (.getAbsolutePath jar))) -;; TODO: move to lancet? -(defn ^:internal make-path - "Constructs an ant Path object from Files and strings." - [& paths] - (let [ant-path (Path. nil)] - (doseq [path paths] - (.addExisting ant-path (Path. nil (str path)))) - ant-path)) - (defn get-classpath "Answer a list of classpath entries for PROJECT." [project] @@ -55,7 +45,7 @@ (:resources-path project)] (:extra-classpath-dirs project) (checkout-deps-paths project) - (find-jars project) + (find-deps-files project) (if-not (no-dev?) (user-plugins)))) diff --git a/src/leiningen/clean.clj b/src/leiningen/clean.clj index 85bffcc6c..73a86f69f 100644 --- a/src/leiningen/clean.clj +++ b/src/leiningen/clean.clj @@ -20,6 +20,5 @@ Set :extra-files-to-clean in project.clj to delete other files. Dependency jars are not deleted; run deps task to delete all jars and get fresh ones." [project] - (println "Cleaning up.") (doseq [f (files-to-clean project)] (delete-file-recursively f :silently))) diff --git a/src/leiningen/compile.clj b/src/leiningen/compile.clj index 7831440b2..15808bb4e 100644 --- a/src/leiningen/compile.clj +++ b/src/leiningen/compile.clj @@ -1,26 +1,20 @@ (ns leiningen.compile "Compile Clojure source into .class files." - (:use [leiningen.deps :only [deps find-jars]] + (:use [leiningen.deps :only [deps find-deps-files]] [leiningen.core :only [defdeprecated user-settings *interactive?*]] [leiningen.javac :only [javac]] - [leiningen.classpath :only [make-path get-classpath]] - [clojure.java.io :only [file resource reader]] + [leiningen.classpath :only [get-classpath-string]] + [clojure.java.io :only [file resource reader copy]] [leiningen.util.ns :only [namespaces-in-dir]]) - (:require [leiningen.util.paths :as paths] - [lancet.core :as lancet]) + (:require [leiningen.util.paths :as paths]) (:refer-clojure :exclude [compile]) - (:import (java.io PushbackReader) - (org.apache.tools.ant.taskdefs Java) - (org.apache.tools.ant.types ZipFileSet) - (java.lang.management ManagementFactory) - (java.util.regex Pattern) - (org.apache.tools.ant.types Environment$Variable))) + (:import (java.io PushbackReader))) (declare compile) -(def *silently* false) +(def ^{:dynamic true} *silently* false) -(def *skip-auto-compile* false) +(def ^{:dynamic true} *skip-auto-compile* false) (defn- regex? "Returns true if we have regex class" @@ -42,6 +36,11 @@ n)) nses))) + +(defn- compile-main? [{:keys [main source-path] :as project}] + (and main (not (:skip-aot (meta main))) + (.exists (file source-path (paths/ns->path main))))) + (defn compilable-namespaces "Returns a seq of the namespaces that are compilable, regardless of whether their class files are present and up-to-date." @@ -52,7 +51,7 @@ nses (if (= :all nses) (namespaces-in-dir (:source-path project)) (find-namespaces-by-regex project nses))] - (if (and (:main project) (not (:skip-aot (meta (:main project))))) + (if (compile-main? project) (conj nses (:main project)) nses))) @@ -70,6 +69,8 @@ (.lastModified class-file))))) (compilable-namespaces project))) + ;; eval-in-project + (defdeprecated get-os paths/get-os) (defdeprecated get-os paths/get-arch) @@ -79,11 +80,26 @@ "NUL" "/dev/null"))) -(defn- get-jvm-args [project] - (remove empty? - `(~@(when-let [opts (System/getenv "JVM_OPTS")] [opts]) - ~@(:jvm-opts project) - ~@(:jvm-opts (user-settings))))) +(defn- as-str [x] + (if (instance? clojure.lang.Named x) + (name x) + (str x))) + +(defn- d-property [[k v]] + (format "-D%s=%s" (as-str k) v)) + +(defn ^{:internal true} get-jvm-args [project] + (let [native-arch-path (paths/native-arch-path project)] + `(~@(let [opts (System/getenv "JVM_OPTS")] + (when (seq opts) [opts])) + ~@(:jvm-opts project) + ~@(:jvm-opts (user-settings)) + ~@(map d-property {:clojure.compile.path (:compile-path project) + (str (:name project) ".version") (:version project) + :clojure.debug (boolean (or (System/getenv "DEBUG") + (:debug project)))}) + ~@(when (and native-arch-path (.exists native-arch-path)) + [(d-property [:java-library-path native-arch-path])])))) (defn- injected-forms [] (with-open [rdr (-> "robert/hooke.clj" resource reader PushbackReader.)] @@ -104,34 +120,70 @@ ;; non-daemon threads will prevent process from exiting; ;; see http://tinyurl.com/2ueqjkl (finally - (when-not (or (= "1.5" (System/getProperty + (when (and ~(:shutdown-agents project false) + (not= "1.5" (System/getProperty "java.specification.version")) - ~*interactive?*) + ~(not *interactive?*)) (shutdown-agents)))))] ;; work around java's command line handling on windows ;; http://bit.ly/9c6biv This isn't perfect, but works for what's ;; currently being passed; see ;; http://www.perlmonks.org/?node_id=300286 for some of the ;; landmines involved in doing it properly - (if (and (= (paths/get-os) :windows) java) + (if (and (= (paths/get-os) :windows) (not (:eval-in-leiningen project))) (pr-str (pr-str form)) - (prn-str form)))) + (pr-str form)))) -(defn prep [project skip-auto-compile] - (when (and (not (or *skip-auto-compile* skip-auto-compile)) - (empty? (.list (file (:compile-path project))))) +(defn prep [{:keys [compile-path checksum-deps] :as project} skip-auto-compile] + (when (and (not (or *skip-auto-compile* skip-auto-compile)) compile-path + (empty? (.list (file compile-path)))) (binding [*silently* true] (compile project))) - (when (or (empty? (find-jars project)) - (:checksum-deps project)) - (deps project))) + (when (or (empty? (find-deps-files project)) checksum-deps) + (deps project)) + (when compile-path + (.mkdirs (file compile-path)))) -(defn- add-system-property [java key value] - (.addSysproperty java (doto (Environment$Variable.) - (.setKey (name key)) - (.setValue (name value))))) +(defn eval-in-leiningen [project form-string] + ;; bootclasspath workaround: http://dev.clojure.org/jira/browse/CLJ-673 + (require '[clojure walk repl]) + (require '[clojure.java io shell browse]) + (when (:debug project) + (System/setProperty "clojure.debug" "true")) + ;; need to at least pretend to return an exit code + (try (binding [*warn-on-reflection* (:warn-on-reflection project), *ns* *ns*] + (eval (read-string form-string))) + 0 + (catch Exception e + (.printStackTrace e) + 1))) + +(defn- pump [reader out] + (let [buffer (make-array Character/TYPE 1000)] + (loop [len (.read reader buffer)] + (when-not (neg? len) + (.write out buffer 0 len) + (.flush out) + (Thread/sleep 100) + (recur (.read reader buffer)))))) + +;; clojure.java.shell/sh doesn't let you stream out/err +(defn sh [& cmd] + (let [proc (.exec (Runtime/getRuntime) (into-array cmd))] + (with-open [out (reader (.getInputStream proc)) + err (reader (.getErrorStream proc))] + (let [pump-out (doto (Thread. #(pump out *out*)) .start) + pump-err (doto (Thread. #(pump err *err*)) .start)] + (.join pump-out) + (.join pump-err)) + (.waitFor proc)))) + +(defn eval-in-subprocess [project form-string] + (apply sh `(~(or (System/getenv "JAVA_CMD") "java") + "-cp" ~(get-classpath-string project) + ~@(get-jvm-args project) + "clojure.main" "-e" ~form-string))) -;; TODO: split this function up (defn eval-in-project "Executes form in an isolated classloader with the classpath and compile path set correctly for the project. If the form depends on any requires, put them @@ -140,55 +192,24 @@ (when skip-auto-compile (println "WARNING: eval-in-project's skip-auto-compile arg is deprecated.")) (prep project skip-auto-compile) - (if (:eval-in-leiningen project) - (do ;; bootclasspath workaround: http://dev.clojure.org/jira/browse/CLJ-673 - (require '[clojure walk repl]) - (require '[clojure.java io shell browse]) - (when (:debug project) - (System/setProperty "clojure.debug" "true")) - ;; need to at least pretend to return an exit code - (try (binding [*warn-on-reflection* (:warn-on-reflection project) - *ns* *ns*] - (eval (read-string (get-readable-form nil project form init)))) - 0 - (catch Exception e - (.printStackTrace e) - 1))) - (let [java (Java.)] - (.setProject java lancet/ant-project) - (add-system-property java :clojure.compile.path (:compile-path project)) - (add-system-property java (format "%s.version" (:name project)) - (:version project)) - (when (:debug project) - (add-system-property java :clojure.debug true)) - (when (.exists (file (:native-path project))) - (add-system-property java "java.library.path" (:native-path project))) - ;; TODO: remove in 2.0? - (when (and (paths/legacy-native-path project) - (.exists (file (paths/legacy-native-path project)))) - (add-system-property java "java.library.path" - (str (paths/legacy-native-path project)))) - (.setClasspath java (apply make-path (get-classpath project))) - (.setFailonerror java true) - (.setFork java true) - (.setDir java (file (:root project))) - (doseq [arg (get-jvm-args project)] - (.setValue (.createJvmarg java) arg)) - (.setClassname java "clojure.main") - ;; to allow plugins and other tasks to customize - (when handler - (println "WARNING: eval-in-project's handler argument is deprecated.") - (handler java)) - (.setValue (.createArg java) "-e") - (.setValue (.createArg java) (get-readable-form java project form init)) - (.executeJava java)))) + (let [form-string (get-readable-form nil project form init)] + (if (:eval-in-leiningen project) + (eval-in-leiningen project form-string) + (eval-in-subprocess project form-string)))) + + ;; .class file cleanup (defn- has-source-package? - "Test if the class file's package exists as a directory in :source-path." + "Test if the class file's package exists as a directory in source-path." [project f source-path] - (and source-path (.isDirectory (file (.replace (.getParent f) - (:compile-path project) - source-path))))) + (and source-path + (let [[[parent] [_ _ proxy-mod-parent]] + (->> f, (iterate #(.getParentFile %)), + (take-while identity), rest, + (split-with #(not (re-find #"^proxy\$" (.getName %)))))] + (.isDirectory (file (.replace (.getPath (or proxy-mod-parent parent)) + (:compile-path project) + source-path)))))) (defn- class-in-project? [project f] (or (has-source-package? project f (:source-path project)) @@ -223,8 +244,10 @@ (blacklisted-class? project f))] (.delete f)))) + ;; actual task + (defn- status [code msg] - (when-not *silently* + (when-not *silently* ; TODO: should silently only affect success? (binding [*out* (if (zero? code) *out* *err*)] (println msg))) code) @@ -238,7 +261,6 @@ Uses the namespaces specified under :aot in project.clj or those given as command-line arguments." ([project] - (.mkdir (file (:compile-path project))) (when (:java-source-path project) (javac project)) (if (seq (compilable-namespaces project)) diff --git a/src/leiningen/core.clj b/src/leiningen/core.clj index 599d1d551..42113261d 100644 --- a/src/leiningen/core.clj +++ b/src/leiningen/core.clj @@ -7,7 +7,7 @@ (:import (java.io File) (org.apache.maven.artifact.versioning DefaultArtifactVersion))) -(def *interactive?* false) +(def ^{:dynamic true} *interactive?* false) (defmacro defdeprecated [old new] `(let [new# ~(str (.getName (:ns (meta (resolve new)))) "/" (name new)) @@ -35,7 +35,9 @@ Warning: alpha; subject to change." [] (let [init-file (File. (paths/leiningen-home) "init.clj")] (when (.exists init-file) - (load-file (.getAbsolutePath init-file))))) + (try (load-file (.getAbsolutePath init-file)) + (catch Exception e + (.printStackTrace e)))))) (defn user-settings "Look up the settings map from init.clj or an empty map if it doesn't exist." @@ -74,6 +76,7 @@ Warning: alpha; subject to change." (:deps m#)) :dev-dependencies (or (:dev-dependencies m#) (:dev-deps m#)) + :checksum-deps (:checksum-deps m# true) :compile-path (normalize-path# (or (:compile-path m#) "classes")) :source-path (normalize-path# @@ -128,6 +131,14 @@ Warning: alpha; subject to change." ;; TODO: possibly separate releases/snapshots in 2.0. "clojars" {:url "http://clojars.org/repo/"}}) +;; you can't remove or omit "central", you can only disable it; +;; maven/maven-ant-tasks adds it implicitly, and will continue to +;; report it in the list of checked repositories, even though it's +;; not been consulted. The URL will hopefully be clear enough to users. +(def disabled-central-repo {"central" {:url "http://disabled-central" + :snapshots false + :releases false}}) + (defn- init-settings [id settings] (cond (string? settings) {:url settings} ;; infer snapshots/release policy from repository id @@ -136,12 +147,29 @@ Warning: alpha; subject to change." :else settings)) (defn repositories-for - "Return a map of repositories including or excluding defaults." - [project] - (merge (when-not (:omit-default-repositories project) - default-repos) - (into {} (for [[id settings] (:repositories project)] - [id (init-settings id settings)])))) + "Returns an ordered map of repositories including or excluding defaults. + + By default bases results on contents of :repositories. If another key + is specified via a :kind kwarg, that key will be used to query the + project. e.g. (repositories-for project :kind :deploy-repositories) + will return an ordered map of repositories intended solely for deployment + operations. + + Note: transforming this map via assoc, merge, or similar removes the + order guarantee." + [project & {:keys [kind] :or {kind :repositories}}] + (let [project-repos (for [[id settings] (kind project)] + [id (init-settings id settings)]) + user-deploy-repos (when (= kind :deploy-repositories) + (into [] (:deploy-repositories (user-settings)))) + all-repos (concat + (into [] + (if (:omit-default-repositories project) + disabled-central-repo + default-repos)) + user-deploy-repos + project-repos)] + (apply array-map (mapcat identity all-repos)))) (defn exit "Call System/exit. Defined as a function so that rebinding is possible." @@ -159,6 +187,8 @@ Warning: alpha; subject to change." ;;; Task execution +(def ^{:dynamic true} *current-task* nil) + (def aliases (atom {"--help" "help" "-h" "help" "-?" "help" "-v" "version" "--version" "version" "überjar" "uberjar" "cp" "classpath" "int" "interactive"})) @@ -224,9 +254,10 @@ Warning: alpha; subject to change." (defn apply-task [task-name project args not-found] (let [task (resolve-task task-name not-found)] (if-let [parameters (matching-arity? task-name project args)] - (if (project-accepted? parameters) - (apply task project args) - (apply task args)) + (binding [*current-task* task-name] + (if (project-accepted? parameters) + (apply task project args) + (apply task args))) (let [args (arglists task-name)] (if (and (not project) (project-needed? args)) (abort "Couldn't find project.clj, which is needed for" task-name) @@ -270,14 +301,14 @@ Takes major, minor and incremental versions into account." [project] (when-not (version-greater-eq? (System/getenv "LEIN_VERSION") (:min-lein-version project)) - (do (println (str "\n*** Warning: This project requires Leiningen version " - (:min-lein-version project) - " ***" - "\n*** Using version " (System/getenv "LEIN_VERSION") - " could cause problems. ***\n" - "\n- Get the latest verison of Leiningen at\n" - "- https://github.com/technomancy/leiningen\n" - "- Or by executing \"lein upgrade\"\n\n"))))) + (println (str "\n*** Warning: This project requires Leiningen version " + (:min-lein-version project) + " ***" + "\n*** Using version " (System/getenv "LEIN_VERSION") + " could cause problems. ***\n" + "\n- Get the latest verison of Leiningen at\n" + "- https://github.com/technomancy/leiningen\n" + "- Or by executing \"lein upgrade\"\n\n")))) (defn -main ([task-name & args] diff --git a/src/leiningen/deploy.clj b/src/leiningen/deploy.clj index 0cf3888d8..a3ca98840 100644 --- a/src/leiningen/deploy.clj +++ b/src/leiningen/deploy.clj @@ -16,8 +16,10 @@ (.setArtifact (make-artifact (make-model project))))) (defn- get-repository [project repository-name] - (let [repositories (repositories-for project) - repository (or (repositories repository-name) + (let [deploy-repositories (repositories-for project :kind :deploy-repositories) + repositories (repositories-for project) + repository (or (deploy-repositories repository-name) + (repositories repository-name) {:url repository-name})] (make-repository [repository-name repository]))) @@ -49,6 +51,7 @@ control: (.setFile (file (jar project))) (.addPom (doto (Pom.) (.setMavenProject (make-maven-project project)) + (.setProject lancet/ant-project) (.setFile (file (pom project))))) (.addRemoteRepository (get-repository project repository-name)) (.execute)) diff --git a/src/leiningen/deps.clj b/src/leiningen/deps.clj index 170174198..f0a2184d3 100644 --- a/src/leiningen/deps.clj +++ b/src/leiningen/deps.clj @@ -1,7 +1,9 @@ (ns leiningen.deps "Download all dependencies and put them in :library-path." (:require [lancet.core :as lancet]) - (:use [leiningen.core :only [repositories-for user-settings no-dev?]] + (:use [leiningen.core :only [repositories-for user-settings + *current-task* no-dev?]] + [leiningen.clean :only [clean]] [leiningen.util.maven :only [make-dependency]] [leiningen.util.file :only [delete-file-recursively]] [leiningen.util.paths :only [get-os get-arch]] @@ -94,7 +96,7 @@ ;; private methods, we'll call a public method that we know calls ;; getContainer, getSupportedProtocols. (.getSupportedProtocols deps-task) - (.setBasedir lancet/ant-project (:root project)) + (.setBasedir lancet/ant-project (:root project "")) (.setFilesetId deps-task "dependency.fileset") (.setPathId deps-task (:name project)) (doseq [repo (make-repositories project)] @@ -120,7 +122,8 @@ (defn fetch-deps? [project] (let [deps-checksum-file (new-deps-checksum-file project)] (and (has-dependencies? project) - (or (empty? (.list (File. (:library-path project)))) + (or (= "deps" *current-task*) + (empty? (.list (File. (:library-path project)))) (not (:checksum-deps project (:checksum-deps (user-settings)))) (not (.exists deps-checksum-file)) (not= (slurp deps-checksum-file) (deps-checksum project)))))) @@ -159,41 +162,41 @@ (.listFiles (file (:library-path project)))) ;; TODO: memoize when not in tests -(defn ^{:internal true} find-jars - "Returns a seq of Files for all the jars in the project's library directory." - [project] - (filter #(.endsWith (.getName %) ".jar") - (concat (if (:local-repo-classpath project) +(defn ^{:internal true} find-deps-files [project] + (remove #{(file (:root project) "lib/dev")} + (concat (if (:local-repo-classpath project) ; TODO: default in 2.0 (find-local-repo-jars project) (find-lib-jars project)) ;; This must be hard-coded because it's used in ;; bin/lein and thus can't be changed in project.clj. (.listFiles (file (:root project) "lib/dev"))))) -(def native-subdir (format "native/%s/%s/" (name (get-os)) (name (get-arch)))) +(defn- find-jars [project] + (filter #(.endsWith (.getName %) ".jar") (find-deps-files project))) (defn extract-native-deps [project] (doseq [jar (map #(JarFile. %) (find-jars project)) entry (enumeration-seq (.entries jar)) - :when (.startsWith (.getName entry) native-subdir)] - (let [f (file (:native-path project) - (subs (.getName entry) (count native-subdir)))] + :when (.startsWith (.getName entry) "native/")] + (let [f (file (:native-path project) (subs (.getName entry) + (count "native/")))] (if (.isDirectory entry) (.mkdirs f) (copy (.getInputStream jar entry) f))))) (defn deps "Download :dependencies and put them in :library-path." - [project & _] - (when (seq _) + [project & [skip-dev]] + (when skip-dev (println "WARNING: passing an argument to deps is deprecated.")) (when (fetch-deps? project) (when-not (or (:disable-deps-clean project) (:disable-implicit-clean project)) (delete-file-recursively (:library-path project) :silently) - (delete-file-recursively (File. (:root project) "native") :silently)) + (delete-file-recursively (:native-path project) :silently) + (clean project)) (let [fileset (do-deps project :dependencies)] - (when-not (no-dev?) + (when-not (or skip-dev (no-dev?)) (do-deps project :dev-dependencies)) (extract-native-deps project) (when (:checksum-deps project) diff --git a/src/leiningen/install.clj b/src/leiningen/install.clj index 0d4b58a83..183178458 100644 --- a/src/leiningen/install.clj +++ b/src/leiningen/install.clj @@ -46,17 +46,21 @@ in your local repository. With two arguments, (group/name and version) downloads and installs a project from a remote repository. Places shell wrappers in ~/.lein/bin when provided." ([project] - (let [jarfile (file (jar project)) + (let [jarfile (jar project) model (make-model project) artifact (make-artifact model) installer (.lookup container ArtifactInstaller/ROLE) local-repo (make-local-repo)] ;; for packaging other than "pom" there should be "pom.xml" ;; generated and installed in local repo - (if (not= "pom" (.getPackaging model)) + (when (not= "pom" (.getPackaging model)) (add-metadata artifact (file (pom project)))) - (install-shell-wrappers (JarFile. jarfile)) - (.install installer jarfile artifact local-repo))) + (if (number? jarfile) + ;; if we failed to create the jar, return the status code for exit + jarfile + (do (install-shell-wrappers (JarFile. jarfile)) + (.install installer (file jarfile) artifact local-repo) + 0)))) ([project-name version] (let [[name group] ((juxt name namespace) (symbol project-name)) _ (standalone-download name (or group name) version) diff --git a/src/leiningen/interactive.clj b/src/leiningen/interactive.clj index b8dfcafff..7683000c7 100644 --- a/src/leiningen/interactive.clj +++ b/src/leiningen/interactive.clj @@ -1,6 +1,7 @@ (ns leiningen.interactive "Enter interactive task shell." - (:require [clojure.string :as string]) + (:require [clojure.string :as string] + [clojure.java.io :as io]) (:use [leiningen.core :only [apply-task exit *interactive?*]] [leiningen.test :only [*exit-after-tests*]] [leiningen.repl :only [repl-server repl-socket-on @@ -14,15 +15,14 @@ (defn not-found [& _] (println "That's not a task. Use help to list all tasks.")) -(defn- eval-client-loop [reader writer buffer socket] - (let [len (.read reader buffer) - output (String. buffer)] +(defn- eval-client-loop [reader buffer socket] + (let [len (.read reader buffer)] (when-not (neg? len) (.write *out* buffer 0 len) - (flush) + (.flush *out*) (when-not (.isClosed socket) (Thread/sleep 100) - (recur reader writer buffer socket))))) + (recur reader buffer socket))))) (defn eval-in-repl [connect project form & [_ _ init]] (let [[reader writer socket] (connect)] @@ -30,8 +30,7 @@ (pr-str form) "\n" ' (.close *in*) ")\n")) (.flush writer) - (try (eval-client-loop reader writer - (make-array Character/TYPE 1000) socket) + (try (eval-client-loop reader (make-array Character/TYPE 1000) socket) 0 (catch Exception e (.printStackTrace e) 1) @@ -59,16 +58,19 @@ (recur (.readLine *in*)))))) (defn interactive - "Enter an interactive task shell." + "Enter an interactive task shell. Aliased to \"int\"." [project] + (.delete (io/file "/tmp/bugger-all")) (let [[port host] (repl-socket-on project)] (println welcome) (future (binding [*interactive?* true] - (eval-in-project project `(do ~(repl-server project host port - :prompt '(constantly "")) - ;; can't stop return value from printing - (symbol ""))))) + (eval-in-project project (repl-server project host port + :prompt '(fn []) + :caught '(fn [t] + (println (.getMessage t)) + (.printStackTrace t) + (.close *in*)))))) (let [connect #(poll-repl-connection port 0 vector)] (binding [eval-in-project (partial eval-in-repl connect) *exit-after-tests* false diff --git a/src/leiningen/jar.clj b/src/leiningen/jar.clj index 6ecb560cf..61b0d70a3 100644 --- a/src/leiningen/jar.clj +++ b/src/leiningen/jar.clj @@ -103,7 +103,8 @@ (zipmap (map str (keys attrs)) (vals attrs)))) (defn skip-file? [file relative-path patterns] - (or (.isDirectory file) + (or (not (.exists file)) + (.isDirectory file) (re-find #"^\.?#" (.getName file)) (re-find #"~$" (.getName file)) (some #(re-find % relative-path) patterns))) @@ -127,8 +128,9 @@ (copy child jar-os)))))) (defmethod copy-to-jar :bytes [project jar-os spec] - (.putNextEntry jar-os (JarEntry. (:path spec))) - (copy (ByteArrayInputStream. (:bytes spec)) jar-os)) + (when-not (some #(re-find % (:path spec)) (:jar-exclusions project)) + (.putNextEntry jar-os (JarEntry. (:path spec))) + (copy (ByteArrayInputStream. (:bytes spec)) jar-os))) (defn write-jar [project out-filename filespecs] (let [manifest (make-manifest project)] @@ -171,6 +173,9 @@ (when (and (:resources-path project) (.exists (file (:resources-path project)))) [{:type :path :path (:resources-path project)}]) + (when (and (:java-source-path project) + (not (:omit-source project))) + [{:type :path :path (:java-source-path project)}]) (when-not (:omit-source project) [{:type :path :path (:source-path project)}]) (shell-wrapper-filespecs project deps-fileset))) @@ -198,11 +203,12 @@ function in that namespace will be used as the main-class for executable jar." ([project jar-name] (when jar-name (println "WARNING: Using the jar task with an argument is deprecated.")) - (binding [compile/*silently* true] - (when (zero? (compile/compile project)) - (let [jar-path (get-jar-filename project (get-default-jar-name project)) - deps-fileset (deps project)] + (let [deps-fileset (deps (assoc project :checksum-deps false)) + status (compile/compile project)] + (if (zero? status) + (let [jar-path (get-jar-filename project (get-default-jar-name project))] (write-jar project jar-path (filespecs project deps-fileset)) (println "Created" jar-path) - jar-path)))) + jar-path) + status))) ([project] (jar project nil))) diff --git a/src/leiningen/javac.clj b/src/leiningen/javac.clj index 495138d1d..d224ed3cd 100644 --- a/src/leiningen/javac.clj +++ b/src/leiningen/javac.clj @@ -5,7 +5,9 @@ (:require [lancet.core :as lancet]) (:import (java.io File))) -(def ^{:doc "Default options for the java compiler."} *default-javac-options* +(def ^{:doc "Default options for the java compiler." + :dynamic true} + *default-javac-options* {:debug "false" :fork "true" :includejavaruntime "yes" :includeantruntime "false" diff --git a/src/leiningen/new.clj b/src/leiningen/new.clj index 419d6dcfd..d4cfcdcf8 100644 --- a/src/leiningen/new.clj +++ b/src/leiningen/new.clj @@ -1,6 +1,6 @@ (ns leiningen.new "Create a new project skeleton." - (:use [leiningen.core :only [abort user-settings]] + (:use [leiningen.core :only [abort]] [leiningen.util.paths :only [ns->path]] [clojure.java.io :only [file]] [clojure.string :only [join]]) @@ -16,10 +16,9 @@ (format-map settings))))) (defn write-project [project-dir project-name] - (let [default-settings {:dependencies [['org.clojure/clojure "1.2.1"]]} + (let [default-settings {:dependencies [['org.clojure/clojure "1.3.0"]]} settings (merge-with #(if %2 %2 %1) - default-settings - (user-settings))] + default-settings)] (.mkdirs (file project-dir)) (spit (file project-dir "project.clj") (str "(defproject " project-name " \"1.0.0-SNAPSHOT\"\n" @@ -77,10 +76,12 @@ test-ns (str prefix ".test.core") project-clj (ns->path project-ns)] (spit (file project-dir ".gitignore") - (apply str (interleave ["pom.xml" "*jar" "/lib/" "/classes/" - ".lein-failures" ".lein-deps-sum"] + (apply str (interleave ["/pom.xml" "*jar" "/lib" "/classes" + "/native" "/.lein-failures" "/checkouts" + "/.lein-deps-sum"] (repeat "\n")))) (write-implementation project-dir project-clj project-ns) (write-test project-dir test-ns project-ns) (write-readme project-dir artifact-id) - (println "Created new project in:" project-dir))))) + (println "Created new project in:" project-dir) + (println "Look over project.clj and start coding in" project-clj))))) diff --git a/src/leiningen/plugin.clj b/src/leiningen/plugin.clj index 2bf53f78d..a24d39bc4 100644 --- a/src/leiningen/plugin.clj +++ b/src/leiningen/plugin.clj @@ -21,14 +21,34 @@ (defn extract-name-and-group [project-name] ((juxt name namespace) (symbol project-name))) +(defn uninstall + "Delete the plugin jarfile +Syntax: lein plugin uninstall [GROUP/]ARTIFACT-ID VERSION" + [project-name version] + (let [[name group] (extract-name-and-group project-name) + jarfile (file plugins-path + (plugin-standalone-filename group name version))] + (if (.exists jarfile) + (if (.delete jarfile) + (println (format "Uninstalled %s %s." project-name version)) + (abort (format "Failed to delete \"%s\"." (.getAbsolutePath jarfile)))) + (abort (format "Plugin \"%s %s\" doesn't appear to be installed." + project-name version))))) + +(defn- uninstall-old [project-name] + (doseq [plugin (.list plugins-path) + :let [pat (re-pattern (format "^\\Q%s\\E-.*\\.jar$" project-name))] + :when (re-find pat plugin)] + (.delete (file plugins-path plugin)))) + ;; TODO: extract shared behavior between this and the install task (defn install - "Download, package, and install plugin jarfile into - ~/.lein/plugins + "Download, package, and install plugin jarfile into ~/.lein/plugins Syntax: lein plugin install [GROUP/]ARTIFACT-ID VERSION You can use the same syntax here as when listing Leiningen dependencies." [project-name version] + (uninstall-old project-name) (leiningen.install/install project-name version) (.mkdirs plugins-path) (let [[name group] (extract-name-and-group project-name) @@ -38,7 +58,8 @@ Syntax: lein plugin install [GROUP/]ARTIFACT-ID VERSION _ (extract-jar (file jarfile) temp-project) project (read-project (str (file temp-project "project.clj"))) standalone-filename (plugin-standalone-filename group name version)] - (deps (dissoc project :dev-dependencies :native-dependencies)) + (deps (dissoc project :dev-dependencies :native-dependencies + :eval-in-leiningen)) (with-open [out (-> (file plugins-path standalone-filename) (FileOutputStream.) (ZipOutputStream.))] @@ -49,20 +70,6 @@ Syntax: lein plugin install [GROUP/]ARTIFACT-ID VERSION (delete-file-recursively temp-project) (println "Created" standalone-filename))) -(defn uninstall - "Delete the plugin jarfile -Syntax: lein plugin uninstall [GROUP/]ARTIFACT-ID VERSION" - [project-name version] - (let [[name group] (extract-name-and-group project-name) - jarfile (file plugins-path - (plugin-standalone-filename group name version))] - (if (.exists jarfile) - (if (.delete jarfile) - (println (format "Uninstalled %s %s." project-name version)) - (abort (format "Failed to delete \"%s\"." (.getAbsolutePath jarfile)))) - (abort (format "Plugin \"%s %s\" doesn't appear to be installed." - project-name version))))) - (defn ^{:doc "Manage user-level plugins." :help-arglists '([subtask project-name version]) :subtasks [#'install #'uninstall]} diff --git a/src/leiningen/repl.clj b/src/leiningen/repl.clj index 9a5da3d70..fc90d2e0d 100644 --- a/src/leiningen/repl.clj +++ b/src/leiningen/repl.clj @@ -3,14 +3,14 @@ (:require [clojure.main]) (:use [leiningen.core :only [exit user-settings *interactive?*]] [leiningen.compile :only [eval-in-project]] - [leiningen.deps :only [find-jars deps]] + [leiningen.deps :only [find-deps-files deps]] [leiningen.trampoline :only [*trampoline?*]] [clojure.java.io :only [copy]]) (:import (java.net Socket InetAddress ServerSocket SocketException) (java.io OutputStreamWriter InputStreamReader File PrintWriter) (clojure.lang LineNumberingPushbackReader))) -(def *retry-limit* 200) +(def retry-limit 200) (defn repl-options [project options] (let [options (apply hash-map options) @@ -55,12 +55,13 @@ (let [server# (ServerSocket. ~port 0 (InetAddress/getByName ~host)) acc# (fn [s#] (let [ins# (.getInputStream s#) - outs# (.getOutputStream s#)] + outs# (.getOutputStream s#) + out-writer# (OutputStreamWriter. outs#)] (doto (Thread. #(binding [*in* (-> ins# InputStreamReader. LineNumberingPushbackReader.) - *out* (OutputStreamWriter. outs#) - *err* *err* + *out* out-writer# + *err* (PrintWriter. out-writer#) *warn-on-reflection* ~(:warn-on-reflection project)] (clojure.main/repl @@ -105,7 +106,7 @@ (defn poll-repl-connection ([port retries handler] - (when (> retries *retry-limit*) + (when (> retries retry-limit) (throw (Exception. "Couldn't connect"))) (Thread/sleep 100) (let [val (try (connect-to-server (Socket. "localhost" port) handler) @@ -132,7 +133,7 @@ A socket-repl will also be launched in the background on a socket based on the directory will start a standalone repl session." ([] (repl nil)) ([project] - (when (and project (or (empty? (find-jars project)) + (when (and project (or (empty? (find-deps-files project)) (:checksum-deps project))) (deps project)) (let [[port host] (repl-socket-on project) @@ -140,9 +141,9 @@ directory will start a standalone repl session." (concat (:repl-options project) (:repl-options (user-settings)))) ;; TODO: make this less awkward when we can break poll-repl-connection - retries (- *retry-limit* (or (:repl-retry-limit project) + retries (- retry-limit (or (:repl-retry-limit project) ((user-settings) :repl-retry-limit) - *retry-limit*))] + retry-limit))] (if *trampoline?* (eval-in-project project server-form) (do (future (if (empty? project) diff --git a/src/leiningen/run.clj b/src/leiningen/run.clj index d9bf397fc..cb9086422 100644 --- a/src/leiningen/run.clj +++ b/src/leiningen/run.clj @@ -1,23 +1,30 @@ (ns leiningen.run "Run a -main function with optional command-line arguments." (:use [leiningen.compile :only [eval-in-project]] - [leiningen.core :only [abort]])) + [leiningen.core :only [abort]]) + (:import (java.io FileNotFoundException) + (clojure.lang Reflector))) -(defn- get-ns-and-fn [given] - (if (= 'clojure.main given) ; special-case this oddity - ['clojure.main 'main] - (let [[given-ns given-sym] ((juxt namespace name) given)] - (map symbol (if given-ns - [given-ns given-sym] - [given-sym "-main"]))))) +(defn- normalize-main [given] + (if (namespace (symbol given)) + (symbol given) + (symbol (name given) "-main"))) + +(defn- run-form [given args] + `(let [v# (resolve '~(normalize-main given))] + (if (ifn? v#) + (v# ~@args) + (Reflector/invokeStaticMethod + ~(name given) "main" (into-array [(into-array String '~args)]))))) (defn- run-main "Loads the project namespaces as well as all its dependencies and then calls ns/f, passing it the args." - ([project given-main & args] - (let [[main-ns main-fn] (get-ns-and-fn (symbol given-main))] - (eval-in-project project `((ns-resolve '~main-ns '~main-fn) ~@args) - nil nil `(require '~main-ns))))) + [project given & args] + (eval-in-project project (run-form given args) + nil nil `(try (require '~(symbol (namespace + (normalize-main given)))) + (catch FileNotFoundException _#)))) (defn ^{:help-arglists '([])} run "Run the project's -main function. diff --git a/src/leiningen/search.clj b/src/leiningen/search.clj index 7a88767e6..027aa50f7 100644 --- a/src/leiningen/search.clj +++ b/src/leiningen/search.clj @@ -27,7 +27,7 @@ (defn- download-index [[id {url :url}]] (with-open [stream (.openStream (remote-index-url url))] - (println "Downloading index from" id "-" url) + (println "Downloading index from" id "-" url "... this may take a while.") (let [tmp (java.io.File/createTempFile "lein" "index")] (try (io/copy stream tmp) (unzip tmp (index-location url)) @@ -45,7 +45,7 @@ ;;; Searching -(def ^{:private true} page-size (:search-page-size (user-settings) 10)) +(def ^{:private true} page-size (:search-page-size (user-settings) 25)) (defn search-repository [[id {:keys [url]} :as repo] query page] (if (ensure-fresh-index repo) @@ -69,18 +69,24 @@ (defn- print-results [[id] results page] (when (seq results) (println " == Results from" id "-" "Showing page" page "/" - ;; TODO: divide by page size? - (:_total-hits (meta results) page-size) "total") + (-> results meta :_total-hits (/ page-size) Math/ceil int) "total") (doseq [result (map parse-result results)] (apply println result)) (prn))) (defn ^{:help-arglists '([query] [query page])} search - "Search remote repositories. + "Search remote maven repositories for matching jars. -The first run will download a set of indices, which will take a -while. Pass in --update as the query to force a fresh download of all -indices. Also accepts a second parameter for fetching successive pages." +The first run will download a set of indices, which will take a while. +Pass in --update as the query to force a fresh download of all +indices. + +The query is evaluated as a lucene search. You can search for simple +string matches or do more advanced queries such as this +'lein search \"clojure AND http AND NOT g:org.clojars*\"' + +Also accepts a second parameter for fetching successive +pages." ;; support running outside project ([query] (search {} query)) ([project-or-query query-or-page] diff --git a/src/leiningen/test.clj b/src/leiningen/test.clj index a565cb41b..95bcf7a0f 100644 --- a/src/leiningen/test.clj +++ b/src/leiningen/test.clj @@ -8,7 +8,7 @@ (:import (java.io File))) ;; TODO: switch to using *interactive* flag in 2.0. -(def *exit-after-tests* true) +(def ^{:dynamic true} *exit-after-tests* true) (defn- form-for-hook-selectors [selectors] `(when (seq ~selectors) @@ -45,7 +45,7 @@ each namespace and print an overall summary." (java.io.OutputStreamWriter.))] (.write w# (pr-str summary#))) (when (or ~*exit-after-tests* (not ~*interactive?*)) - (System/exit 0)))))) + (System/exit (+ (:error summary#) (:fail summary#)))))))) (defn- read-args [args project] (let [args (map read-string args) diff --git a/src/leiningen/trampoline.clj b/src/leiningen/trampoline.clj index 2eda26b6c..e0b74a444 100644 --- a/src/leiningen/trampoline.clj +++ b/src/leiningen/trampoline.clj @@ -1,45 +1,24 @@ (ns leiningen.trampoline (:refer-clojure :exclude [trampoline]) (:use [leiningen.core :only [apply-task task-not-found abort]] - [leiningen.compile :only [get-readable-form prep eval-in-project]] + [leiningen.compile :only [sh]] [leiningen.classpath :only [get-classpath-string]]) (:require [clojure.string :as string] [clojure.java.io :as io] + [clojure.java.shell :as shell] [leiningen.util.paths :as paths])) -(def *trampoline?* false) - -(defn get-jvm-opts [project] - (let [legacy-native (paths/legacy-native-path project)] - (filter identity [(if (not (empty? (System/getenv "JVM_OPTS"))) - (System/getenv "JVM_OPTS")) - (if (:debug project) - "-Dclojure.debug=true") - (if (.exists (io/file (:native-path project))) - (str "-Djava.library.path=" (:native-path project)) - (if (.exists (io/file legacy-native)) - (str "-Djava.library.path=" legacy-native)))]))) +(def ^{:dynamic true} *trampoline?* false) (defn win-batch? [] (.endsWith (System/getProperty "leiningen.trampoline-file") ".bat")) -(defn escape [form-string] - (if (win-batch?) - form-string - (format "\"%s\"" (.replaceAll form-string "\"" "\\\\\"")))) - -(defn quote-path [string] - (format "\"%s\"" (if (win-batch?) string (.replace string "\\" "\\\\")))) - -(defn command-string [project java-cmd jvm-opts [form init]] - (string/join " " [(quote-path java-cmd) - "-cp" (quote-path (get-classpath-string project)) - (string/join " " (map quote-path jvm-opts)) - "clojure.main" "-e" - (escape (get-readable-form (win-batch?) project form init))])) - (defn write-trampoline [command] - (spit (System/getProperty "leiningen.trampoline-file") command)) + (spit (System/getProperty "leiningen.trampoline-file") + (string/join " " (if (win-batch?) + command + (conj (vec (butlast command)) + (with-out-str (prn (last command)))))))) (defn trampoline "Run a task without nesting the project's JVM inside Leiningen's. @@ -48,21 +27,17 @@ Calculates what needs to run in the project's process for the provided task and runs it after Leiningen's own JVM process has exited rather than as a subprocess of Leiningen's project. -Use this to save memory or to work around things like Ant's stdin -issues. Not compatible with chaining. +Use this to save memory or to work around things like stdin issues. +Not compatible with chaining. ALPHA: subject to change without warning." [project task-name & args] - (let [java-cmd (format "%s/bin/java" (System/getProperty "java.home")) - jvm-opts (get-jvm-opts project) - eval-args (atom nil)] + (let [command (atom nil)] (when (:eval-in-leiningen project) (println "Warning: trampoline has no effect with :eval-in-leiningen.")) (binding [*trampoline?* true - eval-in-project (fn [project form & [_ _ init]] - (prep project true) - (reset! eval-args [form init]) 0)] + sh (fn [& c] (reset! command c) 0)] (apply-task task-name project args task-not-found)) - (if @eval-args - (write-trampoline (command-string project java-cmd jvm-opts @eval-args)) - (abort task-name "is not trampolineable.")))) + (if @command + (write-trampoline @command) + (abort task-name "did not run any project code for trampolining.")))) diff --git a/src/leiningen/uberjar.clj b/src/leiningen/uberjar.clj index dd27f519c..93fef6b63 100644 --- a/src/leiningen/uberjar.clj +++ b/src/leiningen/uberjar.clj @@ -71,7 +71,10 @@ Includes the contents of each of the dependency jars. Suitable for standalone distribution. -With an argument, the uberjar will be built with an alternate main." +With an argument, the uberjar will be built with an alternate main. + +The namespace you choose as main should have :gen-class in its ns form +as well as defining a -main function." ([project main] (when-not (:disable-implicit-clean project) (clean project)) diff --git a/src/leiningen/util/file.clj b/src/leiningen/util/file.clj index 638cfe76b..0a1f90d9d 100644 --- a/src/leiningen/util/file.clj +++ b/src/leiningen/util/file.clj @@ -11,6 +11,7 @@ "Delete file f. If it's a directory, recursively delete all its contents. Raise an exception if any deletion fails unless silently is true." [f & [silently]] + (System/gc) ; This sometimes helps release files for deletion on windows. (let [f (file f)] (if (.isDirectory f) (doseq [child (.listFiles f)] diff --git a/src/leiningen/util/maven.clj b/src/leiningen/util/maven.clj index 0bc434f03..60b0d4dee 100644 --- a/src/leiningen/util/maven.clj +++ b/src/leiningen/util/maven.clj @@ -141,12 +141,12 @@ (defn make-dependency "Makes a dependency from a seq. The seq (usually a vector) should -contain a symbol to define the group and artifact id, then a version -string. The remaining arguments are combined into a map. The value for -the :classifier key (if present) is the classifier on the -dependency (as a string). The value for the :exclusions key, if -present, is a seq of symbols, identifying group ids and artifact ids -to exclude from transitive dependencies." + contain a symbol to define the group and artifact id, then a version + string. The remaining arguments are combined into a map. The value + for the :classifier key (if present) is the classifier on the + dependency (as a string). The value for the :exclusions key, if + present, is a seq of symbols, identifying group ids and artifact ids + to exclude from transitive dependencies." ([dependency] (make-dependency dependency {})) ([dependency project] diff --git a/src/leiningen/util/ns.clj b/src/leiningen/util/ns.clj index 6a66ab929..3b1cdb34b 100644 --- a/src/leiningen/util/ns.clj +++ b/src/leiningen/util/ns.clj @@ -23,10 +23,9 @@ (and (.isFile f) (.endsWith (.getName f) ".jar"))) (defn read-ns-form [r f] - (let [form (try (read r false ::done) - (catch Exception e - (println (format "Couldn't parse %s: %s" f (.getMessage e))) - ::done))] + ;; bug in Clojure 1.2 allows reading "{foo}" and throws when str'd + (let [form (try (doto (read r false ::done) str) + (catch Exception e ::done))] (if (and (list? form) (= 'ns (first form))) form (when-not (= ::done form) diff --git a/src/leiningen/util/paths.clj b/src/leiningen/util/paths.clj index 5fcd9ee5f..7f61cbb76 100644 --- a/src/leiningen/util/paths.clj +++ b/src/leiningen/util/paths.clj @@ -29,10 +29,8 @@ [] (get-by-pattern native-names (System/getProperty "os.arch"))) -(defn legacy-native-path - "Deeply-nested path to native libraries used by native-deps plugin. - Kept for backwards-compatibility; libraries are encouraged to switch - to Leiningen's improved built-in native dependency support." +(defn native-arch-path + "Path to the os/arch-specific directory containing native libs." [project] (when (and (get-os) (get-arch)) (file (:native-path project) (name (get-os)) (name (get-arch))))) diff --git a/test/leiningen/test/compile.clj b/test/leiningen/test/compile.clj index f653f8723..134f7cad4 100644 --- a/test/leiningen/test/compile.clj +++ b/test/leiningen/test/compile.clj @@ -2,11 +2,11 @@ (:refer-clojure :exclude [compile]) (:use [clojure.test] [clojure.java.io :only [file]] - [clojure.java.shell :only [with-sh-dir sh]] + [clojure.java.shell :only [with-sh-dir]] [leiningen.compile] [leiningen.core :only [read-project]] [leiningen.test.helper :only [sample-project sample-failing-project - tricky-name-project]] + tricky-name-project dev-deps-project]] [leiningen.util.file :only [delete-file-recursively]])) (use-fixtures :each (fn [f] @@ -25,6 +25,7 @@ (deftest test-plugin (is (zero? (eval-in-project (assoc sample-project :eval-in-leiningen true + :skip-shutdown-agents true :main nil) '(do (require 'leiningen.compile) :compiled))))) @@ -32,6 +33,9 @@ (deftest test-cleared-transitive-aot (is (zero? (compile (assoc sample-project :clean-non-project-classes true)))) + (is (zero? (eval-in-project sample-project + '(require 'nom.nom.nom))) + "can't load after compiling") (let [classes (seq (.list (file "test_projects" "sample" "classes" "nom" "nom")))] (doseq [r [#"nom\$fn__\d+.class" #"nom\$loading__\d+__auto____\d+.class" @@ -56,10 +60,13 @@ "sample2" "alt__init.class")))) (deftest test-skip-aot-on-main - (delete-file-recursively (file (:root tricky-name-project) "classes") :silent) + (delete-file-recursively (:compile-path tricky-name-project) :silent) (is (zero? (compile tricky-name-project))) - (is (empty? (.list (file (:root tricky-name-project) "classes"))))) + (is (empty? (.list (file (:compile-path tricky-name-project)))))) (deftest test-injection (is (zero? (eval-in-project sample-project '#'leiningen.util.injected/add-hook)))) + +(deftest test-compile-java-main + (is (zero? (compile dev-deps-project)))) diff --git a/test/leiningen/test/core.clj b/test/leiningen/test/core.clj index b32e8b7a8..cdbf91f27 100644 --- a/test/leiningen/test/core.clj +++ b/test/leiningen/test/core.clj @@ -48,3 +48,28 @@ (is (version-greater-eq? "1.2.3" "1.1.1")) (is (version-greater-eq? "1.2.0" "1.2")) (is (version-greater-eq? "1.2" "1"))) + +(deftest test-repositories-for-omitting-central + (let [repos (repositories-for + {:omit-default-repositories true + :repositories {"repo-1" "http://repo-1-url"}})] + (is (= ["http://disabled-central" "http://repo-1-url"] + (map :url (vals repos)))))) + +(deftest test-repositories-for-including-defaults + (let [repos (repositories-for sample-project)] + (is (get repos "central")) + (is (get repos "clojars")) + (is (get repos "snapshots")))) + +(deftest test-repositories-for-many-repos-ordered + (let [repo-names (map #(str "repo-" %) (range 20)) + fake-url-ify #(str % "-url") + repos (repositories-for + {:omit-default-repositories true + :repositories (map #(vector % (fake-url-ify %)) + repo-names)})] + (is (= clojure.lang.PersistentArrayMap (class repos))) + (is (= {:url "repo-11-url"} (get repos "repo-11"))) + (is (= (map fake-url-ify repo-names) (rest (map :url (vals repos))))))) + diff --git a/test/leiningen/test/deploy.clj b/test/leiningen/test/deploy.clj index 6186446c6..99df0249b 100644 --- a/test/leiningen/test/deploy.clj +++ b/test/leiningen/test/deploy.clj @@ -6,23 +6,39 @@ [leiningen.util.file :only [delete-file-recursively tmp-dir]] [leiningen.test.helper :only [sample-project]])) -(deftest test-deploy - (delete-file-recursively (format "%s/lein-repo" tmp-dir) :silently) - (deploy sample-project "snapshots") - (let [dir (file tmp-dir "lein-repo/nomnomnom/nomnomnom/0.5.0-SNAPSHOT/") - files (.list dir) - year (+ 1900 (.getYear (java.util.Date.)))] - (is (seq files)) - ;; TODO: this is vulnerable to the y3k bug! - (is (seq (filter #(re-find #"nomnomnom-0.5.0-2\d{7}\." %) files))))) +(defn- repo-path + [relative-repo-path] + (format "%s/%s" tmp-dir relative-repo-path)) -(deftest test-deploy-custom-url - (delete-file-recursively (format "%s/lein-custom-repo" tmp-dir) :silently) - (let [custom-repo (str tmp-dir "/lein-custom-repo")] - (deploy sample-project (str "file://" custom-repo)) - (let [dir (file custom-repo "nomnomnom/nomnomnom/0.5.0-SNAPSHOT/") - files (.list dir) - year (+ 1900 (.getYear (java.util.Date.)))] +(defn- repo-url + [absolute-repo-path] + (str "file://" absolute-repo-path)) + +(defn- deploy-snapshots + [project relative-repo-path & [explicit-deploy-repo?]] + (let [repo-path (repo-path relative-repo-path) + repo-url (repo-url repo-path)] + (delete-file-recursively repo-path :silently) + (deploy project (if explicit-deploy-repo? + repo-url + "snapshots")) + (let [dir (file repo-path "nomnomnom/nomnomnom/0.5.0-SNAPSHOT/") + files (.list dir)] (is (seq files)) ;; TODO: this is vulnerable to the y3k bug! (is (seq (filter #(re-find #"nomnomnom-0.5.0-2\d{7}\." %) files)))))) + +(deftest test-deploy + (testing "simple deployment to `snapshots` already defined in project.clj" + (deploy-snapshots sample-project "lein-repo"))) + +(deftest test-deploy-custom-url + (testing "deployment to a repo specified as a URL argument to `deploy`" + (deploy-snapshots sample-project "lein-custom-repo" true))) + +(deftest test-deploy-repositories-key + (testing "preferring repository specified in :deploy-repositories over one specified in :repositories" + (deploy-snapshots (assoc sample-project + :deploy-repositories + {"snapshots" (-> "deploy-only-repo" repo-path repo-url)}) + "deploy-only-repo"))) diff --git a/test/leiningen/test/deps.clj b/test/leiningen/test/deps.clj index 7fea46710..1a666cf67 100644 --- a/test/leiningen/test/deps.clj +++ b/test/leiningen/test/deps.clj @@ -1,27 +1,27 @@ (ns leiningen.test.deps - (:use [leiningen.core :only [read-project defproject]] - [leiningen.deps :only [deps]] + (:use [leiningen.core :only [read-project defproject apply-task]] + [leiningen.deps :only [deps do-deps]] [clojure.test] [clojure.java.io :only [file]] [leiningen.util.file :only [delete-file-recursively]] - [leiningen.util.paths :only [get-os get-arch]] + [leiningen.util.paths :only [get-os get-arch native-arch-path]] [leiningen.test.helper :only [sample-project dev-deps-project m2-dir with-no-log native-project]])) (defn lib-populated? [project re] (some #(re-find re (.getName %)) - (file-seq (file (:root project) "lib")))) + (file-seq (file (:library-path project))))) (deftest test-deps - (delete-file-recursively (file (:root sample-project) "lib") true) + (delete-file-recursively (:library-path sample-project) true) (deps sample-project) (let [jars (set (map #(.getName %) - (.listFiles (file (:root sample-project) "lib"))))] + (.listFiles (file (:library-path sample-project)))))] (doseq [j ["jdom-1.0.jar" "tagsoup-1.2.jar" "rome-0.9.jar"]] (is (jars j))))) (deftest test-dev-deps-only - (delete-file-recursively (file (:root dev-deps-project) "lib") true) + (delete-file-recursively (:library-path dev-deps-project) true) (deps dev-deps-project) (let [jars (set (map #(.getName %) (.listFiles (file (:root dev-deps-project) @@ -36,27 +36,22 @@ ps (assoc sample-project :omit-default-repositories true :repositories {"clojars" {:url "http://clojars.org/repo/" :releases false}}) - clj-time ['clj-time "0.3.0-SNAPSHOT"] + slamhound ['slamhound "1.1.0-SNAPSHOT"] hooke ['robert/hooke "1.0.1"] - deps deps #_(fn [project] - (delete-file-recursively (apply m2-dir clj-time) :quiet) - (delete-file-recursively (apply m2-dir hooke) :quiet) - (leiningen.deps/deps project))] + deps (fn [project] + (delete-file-recursively (apply m2-dir slamhound) :quiet) + (delete-file-recursively (apply m2-dir hooke) :quiet) + (leiningen.deps/deps project))] (deps (assoc pr :dependencies [hooke])) (is (lib-populated? ps #"hooke")) - (deps (assoc ps :dependencies [clj-time])) - (is (lib-populated? ps #"clj-time")) + (deps (assoc ps :dependencies [slamhound])) + (is (lib-populated? ps #"slamhound")) (let [snaps-repo-rel-dep (assoc ps :dependencies [hooke])] (is (thrown? Exception (with-no-log (deps snaps-repo-rel-dep))))) - (let [rel-repo-snaps-dep (assoc pr :dependencies [clj-time])] + (let [rel-repo-snaps-dep (assoc pr :dependencies [slamhound])] (is (thrown? Exception (with-no-log (deps rel-repo-snaps-dep)))))) (finally - ;; Without triggering the GC, joda jar cannot be deleted on - ;; Windows, which causes all sorts of seemingly unrelated test - ;; failures. If anybody knows how to fix this properly, please do - ;; it. - (System/gc) - (delete-file-recursively (file (:root sample-project) "lib"))))) + (delete-file-recursively (:library-path sample-project) :silently)))) (def native-lib-files-map {:linux {:x86 #{"libjri.so" "libjinput-linux.so" "liblwjgl.so" "libopenal.so" @@ -98,5 +93,21 @@ (delete-file-recursively (:native-path native-project) true) (deps native-project) (is (= (conj (get-in native-lib-files-map [(get-os) (get-arch)]) ".gitkeep") - (set (for [f (rest (file-seq (file (:native-path native-project))))] + (set (for [f (rest (file-seq (native-arch-path native-project)))] (.getName f)))))) + +(deftest test-checksum-deps + (delete-file-recursively (:library-path sample-project) true) + (deps (assoc sample-project :checksum-deps true)) + (let [deps-ran (atom false)] + (binding [do-deps (fn [& _] (reset! deps-ran true))] + (deps (assoc sample-project :checksum-deps true)) + (is (not @deps-ran))))) + +(deftest test-explicit-checksum-deps + (delete-file-recursively (:library-path sample-project) true) + (deps (assoc sample-project :checksum-deps true)) + (let [deps-ran (atom false)] + (binding [do-deps (fn [& _] (reset! deps-ran true))] + (apply-task "deps" (assoc sample-project :checksum-deps true) [] #()) + (is @deps-ran)))) diff --git a/test/leiningen/test/install.clj b/test/leiningen/test/install.clj index d1f98e715..de44e4c38 100644 --- a/test/leiningen/test/install.clj +++ b/test/leiningen/test/install.clj @@ -17,7 +17,7 @@ (deftest test-install (delete-file-recursively (m2-dir "nomnomnom" "0.5.0-SNAPSHOT") true) (delete-shell-wrappers) - (install sample-project) + (is (zero? (install sample-project))) (is (not (empty? (.listFiles (m2-dir "nomnomnom" "0.5.0-SNAPSHOT"))))) (is (.exists unix-shell-wrapper)) (if (= :windows (get-os)) diff --git a/test/leiningen/test/jar.clj b/test/leiningen/test/jar.clj index 3d9dc9788..15c9b9b63 100644 --- a/test/leiningen/test/jar.clj +++ b/test/leiningen/test/jar.clj @@ -39,13 +39,15 @@ (deftest test-no-bin-jar (let [jar-file (JarFile. (jar (dissoc sample-project :shell-wrapper))) manifest (manifest-map (.getManifest jar-file))] + (is (nil? (.getEntry jar-file + "META-INF/maven/nomnomnom/nomnomnom/pom.properties"))) (is (nil? (.getEntry jar-file "bin/nom"))) (is (nil? (.getEntry jar-file "bin/nom.bat"))) (is (nil? (manifest "Leiningen-shell-wrapper"))))) (deftest test-jar-fails (binding [*err* (java.io.PrintWriter. (platform-nullsink))] - (is (not (jar sample-failing-project))))) + (is (pos? (jar sample-failing-project))))) (deftest test-no-aot-jar-succeeds (with-out-str diff --git a/test/leiningen/test/javac.clj b/test/leiningen/test/javac.clj index 375adc535..fd5207b2c 100644 --- a/test/leiningen/test/javac.clj +++ b/test/leiningen/test/javac.clj @@ -7,7 +7,7 @@ [leiningen.test.helper :only [dev-deps-project]])) (deftest test-javac - (delete-file-recursively (file (:root dev-deps-project) "classes") true) + (delete-file-recursively (:compile-path dev-deps-project) true) (javac dev-deps-project) (is (.exists (file "test_projects/dev-deps-only/classes" "dev_deps_only" "Junk.class"))) diff --git a/test/leiningen/test/plugin.clj b/test/leiningen/test/plugin.clj index 602d0bc5c..2e360d1f0 100644 --- a/test/leiningen/test/plugin.clj +++ b/test/leiningen/test/plugin.clj @@ -20,20 +20,10 @@ (is (= (extract-name-and-group "tehname") ["tehname" nil]))) -(deftest test-help - (is (= "Manage user-level plugins. - -Subtasks available: -install Download, package, and install plugin jarfile into - ~/.lein/plugins - Syntax: lein plugin install [GROUP/]ARTIFACT-ID VERSION - You can use the same syntax here as when listing Leiningen - dependencies. -uninstall Delete the plugin jarfile - Syntax: lein plugin uninstall [GROUP/]ARTIFACT-ID VERSION - -Arguments: ([subtask project-name version])\n" - (with-out-str (plugin "help"))))) +(deftest test-help-mentions-subtasks + (let [out (with-out-str (plugin "help"))] + (is (re-find #"install" out)) + (is (re-find #"uninstall" out)))) (deftest test-install (with-out-str diff --git a/test/leiningen/test/run.clj b/test/leiningen/test/run.clj index dc637b51f..5317fc590 100644 --- a/test/leiningen/test/run.clj +++ b/test/leiningen/test/run.clj @@ -2,9 +2,10 @@ (:use [clojure.test] [clojure.java.io :only [delete-file]] [leiningen.core :only [read-project]] + [leiningen.javac :only [javac]] [leiningen.run] [leiningen.util.file :only [tmp-dir]] - [leiningen.test.helper :only [tricky-name-project]])) + [leiningen.test.helper :only [tricky-name-project dev-deps-project]])) (def out-file (format "%s/lein-test" tmp-dir)) @@ -33,3 +34,7 @@ (is (= "nom::bbb" (slurp out-file))) (is (zero? (run tricky-name-project "--" "-m"))) (is (= "nom:-m" (slurp out-file)))) + +(deftest test-run-java-main + (javac dev-deps-project) + (is (zero? (run dev-deps-project)))) \ No newline at end of file diff --git a/test_projects/dev-deps-only/project.clj b/test_projects/dev-deps-only/project.clj index a295548cc..c730a50af 100644 --- a/test_projects/dev-deps-only/project.clj +++ b/test_projects/dev-deps-only/project.clj @@ -1,3 +1,4 @@ (defproject dev-deps-only "1.0.0-SNAPSHOT" :java-source-path [["src"] ["src2"]] - :dev-dependencies [[org.clojure/clojure "1.2.0"]]) + :dev-dependencies [[org.clojure/clojure "1.2.0"]] + :main dev_deps_only.Junk) diff --git a/test_projects/dev-deps-only/src/dev_deps_only/Junk.java b/test_projects/dev-deps-only/src/dev_deps_only/Junk.java index 9ad6e23fb..9db6ee026 100644 --- a/test_projects/dev-deps-only/src/dev_deps_only/Junk.java +++ b/test_projects/dev-deps-only/src/dev_deps_only/Junk.java @@ -1,3 +1,7 @@ package dev_deps_only; -class Junk {} +public class Junk { + public static void main(String[] args) { + ; + } +} diff --git a/test_projects/native/.gitignore b/test_projects/native/.gitignore index 69746c8e5..c236a8f82 100644 --- a/test_projects/native/.gitignore +++ b/test_projects/native/.gitignore @@ -1,7 +1,7 @@ pom.xml *jar /lib -/native +/nnnative /classes .lein-failures .lein-deps-sum diff --git a/test_projects/native/project.clj b/test_projects/native/project.clj index 1eee114f6..7f2d59248 100644 --- a/test_projects/native/project.clj +++ b/test_projects/native/project.clj @@ -1,5 +1,6 @@ (defproject project-name "1.0.0-SNAPSHOT" :description "Test support for transitive native dependencies" + :native-path "nnnative" :dependencies [[org.clojure/clojure "1.2.1"] [serial-port "1.0.7"] [penumbra/lwjgl "2.4.2"] diff --git a/test_projects/sample/project.clj b/test_projects/sample/project.clj index 17513ab70..d44cb4d45 100644 --- a/test_projects/sample/project.clj +++ b/test_projects/sample/project.clj @@ -14,6 +14,7 @@ :warn-on-reflection true :shell-wrapper {:main nom.nom.nom :bin "bin/nom"} + :jar-exclusions [#"^META-INF"] :test-selectors {:integration :integration :default (complement :integration) :random (fn [_] (> (rand) ~(float 1/2))) diff --git a/test_projects/sample/src/nom/nom/nom.clj b/test_projects/sample/src/nom/nom/nom.clj index be6854020..807683531 100644 --- a/test_projects/sample/src/nom/nom/nom.clj +++ b/test_projects/sample/src/nom/nom/nom.clj @@ -7,6 +7,8 @@ (throw (Exception. (str "Not running Clojure 1.1.0 Snapshot: " (clojure-version))))) +(def unused-proxy (proxy [Object] [] (toString [] "unused"))) + (defn -main [& args] (when-not (empty? args) (println "NOM! Munched" (first args)) diff --git a/todo.org b/todo.org index 8484db6d5..19429e3f6 100644 --- a/todo.org +++ b/todo.org @@ -7,34 +7,22 @@ Leiningen TODOs See also https://github.com/technomancy/leiningen/issues -* Open Questions - - Aether: how would it integrate? Improvement over maven-ant-tasks? - - Tests in Nailgun: would it require a separate shell script? - - How could nonlinear versions be represented? -* For 2.0 - - [ ] Remove deprecated functionality. - - [ ] Quit using ant's Java task. It is horrible. - Try a custom classloader approach? or an alternate exec. - - [ ] Use Aether instead of maven-ant-tasks? - http://www.sonatype.com/people/2010/08/introducing-aether/ - Could also allow us to fix :omit-default-repositories (#221) - - [ ] Separate out LEIN_JVM_OPTS (#233) - - [ ] classifiers for specifying what clojure version to use? - As more versions of Clojure start to exist, libraries may want to - publish different branches that target different versions of - Clojure itself. Classifiers may be the way to separate these out? - - [ ] improve search query parser - - [ ] add ns-level test selectors - - [ ] improve test coverage - - [ ] suppress useless ant output in classpath calculation - for :local-repo-classpath (#236) +* For 1.6.2 + - [X] resources with eval-in-leiningen (#248) + - [X] fix :omit-default-repositories wrt central (#211) + - [X] deps should run an implicit clean + - [X] don't let multiple versions of a plugin interfere with each other (#301) + - [X] non-jar deps on classpath (#244) + - [X] recover from error in interactive (#234) + - [X] use java class in run task (#249) + - [X] fix deploy with new maven-ant-tasks * For 1.6.1 - [X] upgrade hooke - [X] make it easier to use :repl-options [:print clojure.pprint/pprint] - [X] fix shutdown-agents with repl - [X] don't clear out lib/dev upon jarring (221) - [X] support alternate main namespace for uberjar. -* For 1.6 +* For 1.6.0 - [X] Trampoline functionality - [X] move exit-after-tests check to eval-in-project (discuss on list?) - [X] don't freak out when attempting to download non-existent indices @@ -46,11 +34,7 @@ See also https://github.com/technomancy/leiningen/issues - [X] make :repl-init change initial ns of repl and swank - [X] more flexibility in search results - [X] support ns/name in run task - - [X] add option to use ~/.m2-based classpath instead of copying to - lib/? It looks like it would be easy to stop copying things - into lib/ and just use Maven's notion of the project's - dependencies to construct a classpath that references jars - straight from ~/.m2. + - [X] add option to use ~/.m2-based classpath instead of copying to lib/ - [X] Merge lein-search - [X] Merge lein-retest - [X] Merge native-deps @@ -62,7 +46,7 @@ See also https://github.com/technomancy/leiningen/issues - [X] Revert back to :warn on checksum failure. - [X] Fix LEIN_ROOT warning in bin/lein. - [X] Honor user-settings in more places. -* For 1.5 +* For 1.5.0 - unify auth options between :repositories and :deploy-to - suppress socket closed stacktrace in interactive task - checksum deps set; don't re-download if unchanged @@ -72,7 +56,7 @@ See also https://github.com/technomancy/leiningen/issues - failure to upgrade leaves lein as a zero-length file (#153) - ensure project jar is last in uberjar (#178) - investigate uberjar slowdown? (#160) -* For 1.4 +* For 1.4.0 - socket timing issues with interactive tests - connect to socket repl in interactive task - :uberjar-exclusions? diff --git a/zsh_completion.zsh b/zsh_completion.zsh new file mode 100644 index 000000000..4e2f84b88 --- /dev/null +++ b/zsh_completion.zsh @@ -0,0 +1,69 @@ +#compdef lein + +# Lein ZSH completion function +# Drop this somewhere in your $fpath (like /usr/share/zsh/site-functions) +# and rename it _lein + +_lein() { + if (( CURRENT > 2 )); then + # shift words so _arguments doesn't have to be concerned with second command + (( CURRENT-- )) + shift words + # use _call_function here in case it doesn't exist + _call_function 1 _lein_${words[1]} + else + _values "lein command" \ + "classpath[Print the classpath of the current project.]" \ + "clean[Remove compiled class files and jars from project.]" \ + "compile[Compile Clojure source into \.class files.]" \ + "deploy[Build jar and deploy to remote repository.]" \ + "deps[Download :dependencies and put them in :library-path.]" \ + "help[Display a list of tasks or help for a given task.]" \ + "install[Install current project or download specified project.]" \ + "interactive[Enter an interactive task shell.]" \ + "jack-in[Jack in to a Clojure SLIME session from Emacs.]" \ + "jar[Package up all the project's files into a jar file.]" \ + "javac[Compile Java source files.]" \ + "new[Create a new project skeleton.]" \ + "plugin[Manage user-level plugins.]" \ + "pom[Write a pom.xml file to disk for Maven interop.]" \ + "repl[Start a repl session either with the current project or standalone.]" \ + "retest[Run only the test namespaces which failed last time around.]" \ + "run[Run the project's -main function.]" \ + "search[Search remote repositories.]" \ + "swank[Launch swank server for Emacs to connect.]" \ + "test[Run the project's tests.]" \ + "test![Run a project's tests after cleaning and fetching dependencies.]" \ + "trampoline[Run a task without nesting the project's JVM inside Leiningen's.]" \ + "uberjar[Package up the project files and all dependencies into a jar file.]" \ + "upgrade[Upgrade Leiningen to the latest stable release.]" \ + "version[Print version for Leiningen and the current JVM.]" + fi +} + +_lein_plugin() { + _values "lein plugin commands" \ + "install[Download, package, and install plugin jarfile into ~/.lein/plugins]" \ + "uninstall[Delete the plugin jarfile: \[GROUP/\]ARTIFACT-ID VERSION]" +} + + +_lein_namespaces() { + if [ -d test ]; then + _values "lein valid namespaces" $(find $1 -type f -name "*.clj" -exec grep -E \ + '^\(ns[[:space:]]+\w+' '{}' ';' | awk '/\(ns[ ]*([A-Za-z\.]+)/ {print $2}') + fi +} + +_lein_run() { + _lein_namespaces "src/" +} + +_lein_test() { + _lein_namespaces "test/" +} + +_lein_test!() { + _lein_namespaces "test/" +} +