[gradle-1.12] 06/211: upstream import 0.9~rc3

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:17:48 UTC 2015


This is an automated email from the git hooks/post-receive script.

seamlik-guest pushed a commit to branch master
in repository gradle-1.12.

commit 98ab094da6fec7f23a955d56b30b2e20c42b447e
Author: Miguel Landaeta <miguel at miguel.cc>
Date:   Mon Nov 22 21:39:21 2010 -0430

    upstream import 0.9~rc3
---
 build.gradle                                       |  86 ++++---
 .../gradle/build/docs/BuildableDOMCategory.groovy  |   3 +-
 .../build/docs/dsl/AssembleDslDocTask.groovy       |   3 +-
 .../org/gradle/build/docs/dsl/ClassDoc.groovy      |  10 +-
 gradle.properties                                  |   6 +-
 src/toplevel/changelog.txt                         |   2 +-
 .../gradle-code-quality/code-quality.gradle        |   4 +-
 .../BackwardsCompatibilityIntegrationTest.groovy   |  56 -----
 ...CrossVersionCompatibilityIntegrationTest.groovy |  79 ++++++
 .../integtests/DynamicObjectIntegrationTest.groovy |   9 +-
 .../gradle/integtests/IdeaIntegrationTest.groovy   |  23 +-
 .../OsgiProjectSampleIntegrationTest.groovy        |   2 +
 .../integtests/UserGuideSamplesRunner.groovy       |  36 ---
 .../fixtures/AbstractGradleExecuter.java           |  10 +
 .../fixtures/BasicGradleDistribution.java          |  22 ++
 .../integtests/fixtures/DaemonGradleExecuter.java  |  59 +++++
 .../integtests/fixtures/ForkingGradleExecuter.java |   6 +-
 .../integtests/fixtures/GradleDistribution.java    |  21 +-
 .../fixtures/GradleDistributionExecuter.java       |  33 +--
 .../gradle/integtests/fixtures/GradleExecuter.java |   4 +
 .../fixtures/PreviousGradleVersionExecuter.groovy  |  38 ++-
 .../maven/MavenProjectIntegrationTest.groovy       |  57 ++---
 .../gradle/integtests/maven/MavenRepository.groovy |  48 ++++
 .../canBuildJavaProject/build.gradle               |   0
 .../src/main/groovy/org/gradle/CustomTask.groovy   |   0
 .../src/main/java/org/gradle/Person.java           |   0
 .../shared/build.gradle                            |  11 +
 .../canCreateAndDeleteMetaData/api/build.gradle    |   3 +
 .../expectedFiles/apiClasspath.xml                 |   6 +-
 .../expectedFiles/apiJdt.properties                |  11 +
 .../expectedFiles/groovyprojectClasspath.xml       |   6 +-
 .../expectedFiles/groovyprojectJdt.properties      |  11 +
 .../expectedFiles/javabaseprojectClasspath.xml     |   4 +
 .../expectedFiles/javabaseprojectJdt.properties    |  11 +
 .../expectedFiles/javabaseprojectProject.xml       |  21 ++
 .../expectedFiles/webAppJava6Classpath.xml         |   5 +
 .../expectedFiles/webAppJava6Jdt.properties        |  11 +
 .../expectedFiles/webAppJava6Project.xml           |  39 +++
 .../expectedFiles/webAppJava6WtpComponent.xml      |   8 +
 ...serviceWtpFacet.xml => webAppJava6WtpFacet.xml} |   2 +-
 .../expectedFiles/webAppWithVarsClasspath.xml      |  11 +
 .../expectedFiles/webAppWithVarsJdt.properties     |  11 +
 .../expectedFiles/webAppWithVarsProject.xml        |  39 +++
 ...omponent.xml => webAppWithVarsWtpComponent.xml} |  10 +-
 ...viceWtpFacet.xml => webAppWithVarsWtpFacet.xml} |   2 +-
 .../expectedFiles/webserviceClasspath.xml          |  14 +-
 .../expectedFiles/webserviceJdt.properties         |  11 +
 .../expectedFiles/webserviceWtpComponent.xml       |   3 +-
 .../expectedFiles/webserviceWtpFacet.xml           |   2 +-
 .../groovyproject/build.gradle                     |   1 +
 .../javabaseproject/build.gradle                   |   5 +
 .../canCreateAndDeleteMetaData/master/build.gradle |  84 +++++--
 .../master/settings.gradle                         |   2 +-
 .../webAppJava6/build.gradle                       |   3 +
 .../src/main/java/org/gradle/Person.java           |   4 +
 .../webAppJava6/src/main/webapp/index.html         |   1 +
 .../webAppWithVars/build.gradle                    |   9 +
 .../src/main/java/org/gradle/Person.java           |   4 +
 .../webservice/build.gradle                        |  17 +-
 .../build.gradle                                   |  15 ++
 .../expectedFiles/root.iml.xml                     |  39 +++
 .../root.iml                                       |  20 ++
 .../settings.gradle                                |   1 +
 .../build.gradle                                   |  17 ++
 .../settings.gradle                                |   1 +
 .../build.gradle                                   |  38 +++
 .../settings.gradle                                |   1 +
 .../build.gradle                                   |  19 ++
 .../settings.gradle                                |   1 +
 .../maven/pomGeneration/expectedNewPom.txt         |   6 +
 .../integtests/maven/pomGeneration/expectedPom.txt |   6 +
 .../groovy/org/gradle/BuildExceptionReporter.java  |   9 +-
 .../src/main/groovy/org/gradle/BuildLogger.java    |   8 +-
 .../src/main/groovy/org/gradle/GradleLauncher.java |  29 ++-
 .../src/main/groovy/org/gradle/StartParameter.java |  11 +-
 .../gradle/api/artifacts/maven/MavenDeployer.java  |  49 ++--
 .../api/artifacts/maven/MavenDeployment.java       |  54 ++++
 .../gradle/api/artifacts/maven/MavenResolver.java  |  26 +-
 .../api/internal/AsmBackedClassGenerator.java      |  32 ++-
 .../api/internal/DefaultDomainObjectContainer.java |  10 +-
 .../DefaultNamedDomainObjectContainer.java         |  14 +-
 ...GroovySourceGenerationBackedClassGenerator.java |   1 +
 .../artifacts/publish/AbstractPublishArtifact.java |   5 +
 .../artifacts/publish/ArchivePublishArtifact.java  |   4 -
 .../artifacts/publish/DefaultPublishArtifact.java  |   4 -
 .../artifacts/publish/maven/DefaultMavenPom.java   |   8 +-
 .../DefaultPomDependenciesConverter.java           |  45 ++--
 .../maven/deploy/AbstractMavenResolver.java        |  35 ++-
 .../publish/maven/deploy/ArtifactPom.java          |  12 +-
 .../publish/maven/deploy/ArtifactPomContainer.java |   2 +-
 .../publish/maven/deploy/DefaultArtifactPom.java   | 157 +++++++++---
 .../maven/deploy/DefaultArtifactPomContainer.java  |  16 +-
 .../maven/deploy/DefaultMavenDeployment.java       |  64 +++++
 .../publish/maven/deploy/DeployableFilesInfo.java  |  46 ----
 .../org/gradle/api/internal/plugins/IdePlugin.java |  66 +++++
 .../api/internal/project/ant/BasicAntBuilder.java  |  12 +
 .../api/internal/tasks/DefaultTaskContainer.java   |   2 +-
 .../AbstractPersistableConfigurationObject.java    |  67 +++++
 .../internal/tasks/generator/Generator.java}       |  57 +++--
 .../generator/PersistableConfigurationObject.java} |  12 +-
 .../PersistableConfigurationObjectGenerator.java   |  44 ++++
 .../PropertiesPersistableConfigurationObject.java} |  31 ++-
 .../XmlPersistableConfigurationObject.java         |  62 +++++
 .../groovy/org/gradle/api/invocation/Gradle.java   |  44 +++-
 .../groovy/org/gradle/api/tasks/GeneratorTask.java | 163 ++++++++++++
 .../org/gradle/api/tasks/XmlGeneratorTask.java     |  74 ++++++
 .../api/tasks/diagnostics/ProjectReportTask.java   |   5 +-
 .../configuration/GradleLauncherMetaData.java      |   7 +-
 .../main/groovy/org/gradle/configuration/Help.java |   5 +-
 .../AbstractCommandLineConverter.java              |  12 +
 .../BuildClientMetaData.java}                      |  51 ++--
 .../BuildRequestMetaData.java}                     |  58 +++--
 .../initialization/CommandLineConverter.java       |   6 +-
 .../gradle/initialization/CommandLineParser.java   | 275 +++++++++++++--------
 .../DefaultBuildRequestMetaData.java               |  41 +++
 .../DefaultCommandLineConverter.java               |  21 +-
 .../DefaultGradleLauncherFactory.java              |  29 ++-
 .../GradleLauncherFactory.java                     |  15 +-
 .../gradle/initialization/NestedBuildTracker.java  |   7 +-
 .../gradle/initialization/ParsedCommandLine.java   |  30 ++-
 .../initialization/ParsedCommandLineOption.java    |  12 +-
 .../org/gradle/invocation/DefaultGradle.java       |  20 ++
 .../ActionBroadcast.java}                          |  63 ++---
 .../internal/LoggingCommandLineConverter.java      |   8 +-
 .../messaging/remote/internal/ChannelMessage.java  |   6 +-
 .../ChannelMessageMarshallingDispatch.java         |   8 +-
 .../ChannelMessageUnmarshallingDispatch.java       |   8 +-
 ...utgoingConnector.java => ConnectException.java} |  11 +-
 .../remote/internal/DefaultMessagingClient.java    |   2 +-
 .../remote/internal/DefaultMessagingServer.java    |  12 +-
 .../internal/DefaultMultiChannelConnection.java    |  51 ++--
 .../internal/DefaultMultiChannelConnector.java     |  19 +-
 .../remote/internal/EndOfStreamDispatch.java       |   8 +-
 .../remote/internal/EndOfStreamFilter.java         |   8 +-
 .../remote/internal/EndOfStreamReceive.java        |  10 +-
 .../internal/HandshakeIncomingConnector.java       |  18 +-
 .../remote/internal/IncomingConnector.java         |   4 +-
 .../internal/IncomingMethodInvocationHandler.java  |   4 +-
 .../gradle/messaging/remote/internal/Message.java  |   8 +-
 .../MethodInvocationUnmarshallingDispatch.java     |   4 +-
 .../remote/internal/MultiChannelConnector.java     |   4 +-
 .../remote/internal/OutgoingConnector.java         |   3 +-
 .../internal/OutgoingMethodInvocationHandler.java  |   4 +-
 .../remote/internal/SocketConnection.java          |  17 +-
 .../remote/internal/TcpIncomingConnector.java      |  12 +-
 .../remote/internal/TcpMessagingClient.java        |   2 +-
 .../remote/internal/TcpOutgoingConnector.java      |   9 +-
 .../groovy/org/gradle/profile/ProfileListener.java |   3 -
 .../org/gradle/testfixtures/ProjectBuilder.java    |  10 +-
 .../src/main/groovy/org/gradle/util/Clock.java     |  14 +-
 .../org/gradle/util/ObservableUrlClassLoader.java  |   6 +-
 .../org/gradle/BuildExceptionReporterTest.groovy   |  17 +-
 .../groovy/org/gradle/StartParameterTest.groovy    |  15 +-
 .../groovy/org/gradle/api/GeneratorTaskTest.groovy | 110 +++++++++
 .../api/internal/AbstractClassGeneratorTest.java   |  11 +-
 .../DefaultPomDependenciesConverterTest.java       |  53 +++-
 .../maven/deploy/AbstractMavenResolverTest.java    |  44 ++--
 .../maven/deploy/BaseMavenDeployerTest.java        |   7 +-
 .../maven/deploy/BaseMavenInstallerTest.java       |   3 +-
 .../deploy/DefaultArtifactPomContainerTest.groovy  |  98 ++++++++
 .../deploy/DefaultArtifactPomContainerTest.java    | 132 ----------
 .../maven/deploy/DefaultArtifactPomTest.java       | 218 +++++++++++-----
 .../api/internal/plugins/IdePluginTest.groovy      |  65 +++++
 .../internal/project/DefaultAntBuilderTest.groovy  |  21 +-
 .../project/taskfactory/TaskFactoryTest.java       |  15 +-
 .../internal/tasks/DefaultTaskContainerTest.java   |  21 +-
 ...sistableConfigurationObjectGeneratorTest.groovy |  62 +++++
 ...ertiesPersistableConfigurationObjectTest.groovy |  70 ++++++
 .../XmlPersistableConfigurationObjectTest.groovy   |  70 ++++++
 .../internal/tasks/generator/defaultResource.xml   |   1 +
 .../org/gradle/api/tasks/GradleBuildTest.groovy    |   2 +-
 .../GradleLauncherMetaDataTest.groovy              |  13 +-
 .../initialization/BuildSourceBuilderTest.groovy   |   2 +-
 .../initialization/CommandLineParserTest.groovy    |   9 +-
 .../DefaultCommandLineConverterTest.java           |  74 +++---
 .../DefaultGradleLauncherFactoryTest.groovy        |  92 +++++++
 .../DefaultGradleLauncherFactoryTest.java          |  86 -------
 .../initialization/NestedBuildTrackerTest.groovy   |  19 +-
 .../org/gradle/invocation/DefaultGradleTest.java   |  51 ++++
 .../org/gradle/listener/ActionBroadcastTest.groovy |  48 ++++
 .../ChannelMessageMarshallingDispatchTest.java     |   2 +-
 .../ChannelMessageUnmarshallingDispatchTest.java   |   2 +-
 .../internal/DefaultObjectConnectionTest.java      |  25 +-
 .../messaging/remote/internal/MessageTest.groovy   |   2 +-
 .../remote/internal/TcpConnectorTest.groovy        |  14 +-
 .../test/groovy/org/gradle/util/HelperUtil.groovy  |   4 +-
 .../src/test/groovy/org/gradle/util/Matchers.java  |  32 ++-
 .../tasks/generator/defaultResource.properties     |   1 +
 .../internal/tasks/generator/defaultResource.xml   |   1 +
 subprojects/gradle-docs/docs.gradle                |  12 +-
 subprojects/gradle-docs/src/docs/css/base.css      |  22 +-
 subprojects/gradle-docs/src/docs/css/dsl.css       |  36 +++
 subprojects/gradle-docs/src/docs/css/print.css     |  16 +-
 subprojects/gradle-docs/src/docs/css/style.css     | 131 ----------
 .../src/docs/css/{style.css => userguide.css}      |  70 ------
 .../gradle-docs/src/docs/stylesheets/dslHtml.xsl   |  97 ++++++++
 .../src/docs/stylesheets/standaloneHtml.xsl        |   3 +
 .../src/docs/stylesheets/userGuideHtmlCommon.xsl   |   1 +
 .../src/docs/stylesheets/userGuidePdf.xsl          |   1 +
 .../gradle-docs/src/docs/userguide/commandLine.xml |  99 ++++++--
 .../gradle-docs/src/docs/userguide/dsl/dsl.xml     |  74 +++---
 ....xml => org.gradle.api.tasks.GeneratorTask.xml} |  12 +-
 ...l => org.gradle.api.tasks.XmlGeneratorTask.xml} |   9 -
 .../dsl/org.gradle.plugins.idea.IdeaProject.xml    |  11 +-
 .../src/docs/userguide/eclipsePlugin.xml           | 164 ++++++++----
 .../gradle-docs/src/docs/userguide/ideaPlugin.xml  | 163 ++++++++----
 .../src/docs/userguide/installation.xml            |   8 +-
 .../gradle-docs/src/docs/userguide/mavenPlugin.xml |   2 +-
 .../src/docs/userguide/standardTasks.xml           |   6 +-
 .../gradle-docs/src/docs/userguide/userguide.xml   |   2 -
 .../gradle-docs/src/samples/eclipse/build.gradle   |   6 +-
 .../gradle-docs/src/samples/idea/build.gradle      |   8 +-
 .../src/samples/maven/pomGeneration/build.gradle   |   6 +-
 .../gradle-docs/src/samples/osgi/build.gradle      |   1 +
 .../eclipse/AbstractXmlGeneratorTask.groovy        |  38 ---
 .../gradle/plugins/eclipse/EclipseClasspath.groovy |  66 ++---
 .../org/gradle/plugins/eclipse/EclipseJdt.groovy   |  47 ++++
 .../gradle/plugins/eclipse/EclipsePlugin.groovy    | 101 +++++---
 .../gradle/plugins/eclipse/EclipseProject.groovy   |  54 +---
 .../org/gradle/plugins/eclipse/EclipseWtp.groovy   |  41 ++-
 .../gradle/plugins/eclipse/model/Classpath.groovy  |  51 ++--
 .../org/gradle/plugins/eclipse/model/Facet.groovy  |   3 +
 .../org/gradle/plugins/eclipse/model/Jdt.java      |  71 ++++++
 .../gradle/plugins/eclipse/model/Project.groovy    |  60 ++---
 .../org/gradle/plugins/eclipse/model/Wtp.groovy    |  27 +-
 .../eclipse/model/internal/ClasspathFactory.groovy |  16 +-
 .../eclipse/model/internal/ModelFactory.groovy     |  45 ----
 .../eclipse/model/internal/WtpFactory.groovy       |   6 +-
 .../plugins/eclipse/model/defaultClasspath.xml     |   1 +
 .../eclipse/model/defaultJdtPrefs.properties       |  11 +
 .../plugins/eclipse/model/defaultProject.xml       |   1 +
 .../plugins/eclipse/EclipseClasspathTest.groovy    |  16 --
 .../plugins/eclipse/EclipsePluginTest.groovy       |  41 ++-
 .../plugins/eclipse/EclipseProjectTest.groovy      |  16 --
 .../gradle/plugins/eclipse/EclipseWtpTest.groovy   |   8 +-
 .../plugins/eclipse/model/ClasspathTest.groovy     | 128 ++--------
 .../gradle/plugins/eclipse/model/JdtTest.groovy    |  97 ++++++++
 .../plugins/eclipse/model/ProjectTest.groovy       | 187 ++++----------
 .../gradle/plugins/eclipse/model/WtpTest.groovy    |  30 ++-
 .../org/gradle/plugins/idea/IdeaModule.groovy      | 100 ++------
 .../org/gradle/plugins/idea/IdeaPlugin.groovy      |  40 ++-
 .../org/gradle/plugins/idea/IdeaProject.groovy     |  89 +------
 .../org/gradle/plugins/idea/IdeaWorkspace.groovy   |  51 +---
 .../org/gradle/plugins/idea/model/Jdk.groovy       |   4 +-
 .../org/gradle/plugins/idea/model/Module.groovy    | 106 +++-----
 .../gradle/plugins/idea/model/ModulePath.groovy    |  43 ++--
 .../org/gradle/plugins/idea/model/Path.groovy      |  18 +-
 .../gradle/plugins/idea/model/PathFactory.groovy   |  39 ++-
 .../org/gradle/plugins/idea/model/Project.groovy   |  49 ++--
 .../org/gradle/plugins/idea/model/Workspace.groovy |  22 +-
 .../org/gradle/plugins/idea/IdeaPluginTest.groovy  |   2 +-
 .../plugins/idea/model/ModulePathTest.groovy       |  11 +-
 .../gradle/plugins/idea/model/ModuleTest.groovy    | 212 +++++-----------
 .../plugins/idea/model/PathFactoryTest.groovy      | 104 +++++++-
 .../org/gradle/plugins/idea/model/PathTest.groovy  |   4 +-
 .../gradle/plugins/idea/model/ProjectTest.groovy   | 129 +++-------
 .../gradle/plugins/idea/model/WorkspaceTest.groovy |  80 ------
 .../gradle/launcher/CommandLineActionFactory.java  | 111 +++++----
 .../org/gradle/launcher/DaemonBuildAction.java     |  52 ++++
 .../org/gradle/launcher/DaemonClientAction.java    |  71 ++++++
 .../java/org/gradle/launcher/DaemonConnector.java  | 258 +++++++++++++++++++
 .../main/java/org/gradle/launcher/DaemonMain.java  | 158 ++++++++++++
 ...{BuildCompleter.java => ExecutionListener.java} |  16 +-
 .../{BuildCompleter.java => GradleDaemon.java}     |   6 +-
 .../main/java/org/gradle/launcher/GradleMain.java  |  43 +---
 ...mpleter.java => IncomingConnectionHandler.java} |   7 +-
 .../src/main/java/org/gradle/launcher/Main.java    |  39 ++-
 .../{GradleMain.java => ProcessBootstrap.java}     | 115 ++++-----
 .../java/org/gradle/launcher/RunBuildAction.java   |  51 ++++
 .../java/org/gradle/launcher/StopDaemonAction.java |  46 ++++
 .../java/org/gradle/launcher/protocol/Build.java   |  46 ++++
 .../org/gradle/launcher/protocol/Command.java}     |  27 +-
 .../gradle/launcher/protocol/CommandComplete.java} |  55 +++--
 .../{BuildCompleter.java => protocol/Stop.java}    |  10 +-
 .../launcher/CommandLineActionFactoryTest.groovy   | 120 ++++++---
 .../gradle/launcher/DaemonBuildActionTest.groovy   |  55 +++++
 .../groovy/org/gradle/launcher/MainTest.groovy     |  35 ++-
 .../org/gradle/launcher/RunBuildActionTest.groovy  |  66 +++++
 .../gradle/launcher/StopDaemonActionTest.groovy    |  70 ++++++
 ...rossVersionCompatibilityIntegrationTest.groovy} |  32 ++-
 .../shared/build.gradle                            |   0
 .../shared/settings.gradle                         |   0
 .../internal/plugins/osgi/DefaultOsgiManifest.java |   5 +
 .../plugins/osgi/DefaultOsgiManifestTest.java      |  30 ++-
 wrapper/gradle-wrapper.properties                  |   2 +-
 285 files changed, 6282 insertions(+), 3349 deletions(-)

diff --git a/build.gradle b/build.gradle
index 2ab35b1..3ef888f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -172,8 +172,9 @@ ideaModule {
 ideaProject {
     wildcards += ['?*.gradle']
     javaVersion = '1.6'
-    withXml { node ->
-        // Use git
+    withXml { provider ->
+		def node = provider.asNode()
+		// Use git
         def vcsConfig = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
         vcsConfig.mapping[0].'@vcs' = 'Git'
 
@@ -190,7 +191,8 @@ ideaProject {
 // Exclude resource directories from compilation and add them back in as classpath resources
 
 ideaProject {
-    withXml { node ->
+    withXml { provider ->
+		def node = provider.asNode()
         def compilerConfig = node.component.find { it.'@name' == 'CompilerConfiguration' }
         def exclude = compilerConfig.appendNode('excludeFromCompile')
         Collection resourceDirs = groovyProjects().collect { project -> project.sourceSets*.resources*.srcDirs }.flatten()
@@ -319,52 +321,68 @@ task intTestImage(type: Sync) {
     }
 }
 
-task integTest(type: Test, dependsOn: [intTestImage, binZip, allZip, srcZip, ':docs:userguideDocbook']) {
-    integTestUserDir = file('intTestHomeDir')
-    systemProperties['integTest.userGuideInfoDir'] = project(':docs').docbookSrc
-    systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
-    systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
-    include 'org/gradle/integtests/**/*IntegrationTest.*'
-    forkEvery = 15
-    maxParallelForks = guessMaxForks()
+tasks.withType(Test).allTasks { task ->
+    task.configure {
+        dependsOn intTestImage, binZip, allZip, srcZip, ':docs:userguideDocbook'
+        integTestUserDir = file('intTestHomeDir')
+        systemProperties['integTest.userGuideInfoDir'] = project(':docs').docbookSrc
+        systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
+        systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
+        forkEvery = 15
+        maxParallelForks = guessMaxForks()
+
+        testClassesDir = project(':core').sourceSets.integTest.classesDir
+        classpath = project(':core').sourceSets.integTest.runtimeClasspath + configurations.testRuntime
+        testResultsDir = file("build/test-results/$name")
+        testReportDir = file("build/reports/tests/$name")
+        testSrcDirs = []
 
-    testClassesDir = project(':core').sourceSets.integTest.classesDir
-    classpath = project(':core').sourceSets.integTest.runtimeClasspath + configurations.testRuntime
-    testResultsDir = file('build/test-results')
-    testReportDir = file('build/reports/tests')
-    testSrcDirs = []
-    doFirst {
-        if (isDevBuild()) {
-            exclude 'org/gradle/integtests/DistributionIntegrationTest.*'
-        }
         systemProperties['integTest.gradleHomeDir'] = intTestImage.integTestGradleHome.absolutePath
-        def forkArgs
-        if (noForkIntegTests()) {
-            systemProperties['org.gradle.integtest.fork'] = "false"
-            jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m', '-XX:+HeapDumpOnOutOfMemoryError'
-        } else {
-            jvmArgs '-Xmx512m', '-XX:+HeapDumpOnOutOfMemoryError'
+        jvmArgs '-Xmx512m', '-XX:+HeapDumpOnOutOfMemoryError'
+
+        doFirst {
+            if (isDevBuild()) {
+                exclude 'org/gradle/integtests/DistributionIntegrationTest.*'
+            }
         }
     }
 }
 
-boolean noForkIntegTests() {
-    if (project.hasProperty('forkIntegTests')) {
-        return !Boolean.valueOf(forkIntegTests)
-    }
-    return isDevBuild() && !OperatingSystem.current().isWindows()
+task integTest(type: Test) {
+}
+
+task embeddedIntegTest(type: Test) {
+    systemProperties['org.gradle.integtest.executer'] = 'embedded'
+    jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m', '-XX:+HeapDumpOnOutOfMemoryError'
+}
+
+task daemonIntegTest(type: Test) {
+    systemProperties['org.gradle.integtest.executer'] = 'daemon'
 }
 
 private def isDevBuild() {
     gradle.taskGraph.hasTask(':developerBuild')
 }
 
+gradle.taskGraph.whenReady { graph ->
+    if (isDevBuild()) {
+        if (OperatingSystem.current().isWindows()) {
+            embeddedIntegTest.enabled = false
+        } else {
+            integTest.enabled = false
+        }
+    }
+    if (graph.hasTask(':ciBuild')) {
+        embeddedIntegTest.enabled = false
+    }
+}
+
 def guessMaxForks() {
     int processors = Runtime.runtime.availableProcessors()
     return Math.max(2, (int) (processors / 2))
 }
 
-task testedDists(dependsOn: [assemble, check, integTest, 'openApi:integTest', ':ui:integTest'])
+task testedDists(dependsOn: [assemble, check, integTest, embeddedIntegTest, 'openApi:integTest', ':ui:integTest'])
 
 task nightlyBuild(dependsOn: [clean, testedDists, ':docs:uploadDocs'])
 
@@ -436,13 +454,13 @@ task tag {
 task releaseArtifacts {
     description = 'Builds the release artifacts'
     group = 'release'
-    dependsOn releaseVersion, tag, assemble, ':docs:websiteDocs'
+    dependsOn releaseVersion, assemble, ':docs:websiteDocs'
 }
 
 task release {
     description = 'Builds, tests and uploads the release artifacts'
     group = 'release'
-    dependsOn releaseVersion, releaseArtifacts, testedDists, uploadDists, ':docs:uploadDocs'
+    dependsOn releaseVersion, tag, releaseArtifacts, testedDists, uploadDists, ':docs:uploadDocs'
 }
 
 task wrapper(type: Wrapper, dependsOn: binZip)
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy
index cc993ee..e106bd0 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/BuildableDOMCategory.groovy
@@ -1,6 +1,7 @@
 package org.gradle.build.docs
 
 import org.w3c.dom.Element
+import org.w3c.dom.Node
 
 class BuildableDOMCategory {
     public static setText(Element element, String value) {
@@ -23,7 +24,7 @@ class BuildableDOMCategory {
         cl.call()
     }
 
-    public static leftShift(Element parent, Element node) {
+    public static leftShift(Element parent, Node node) {
         parent.appendChild(parent.ownerDocument.importNode(node, true))
     }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/AssembleDslDocTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/AssembleDslDocTask.groovy
index 094ac82..e5b88b0 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/AssembleDslDocTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/AssembleDslDocTask.groovy
@@ -46,7 +46,7 @@ class AssembleDslDocTask extends DefaultTask {
                 Map<String, ExtensionMetaData> extensions = loadPluginsMetaData()
                 DslModel model = new DslModel(classDocbookDir, document, classpath, classes, extensions)
                 def root = document.documentElement
-                root.table.each { Element table ->
+                root.section[0].table.each { Element table ->
                     insertTypes(table, model)
                 }
             }
@@ -88,6 +88,7 @@ class AssembleDslDocTask extends DefaultTask {
     }
 
     def insertTypes(Element typeTable, DslModel model) {
+        typeTable['@role'] = 'dslTypes'
         typeTable.addFirst {
             thead {
                 tr {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassDoc.groovy
index 3565e3e..bbe3a12 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassDoc.groovy
@@ -1,6 +1,7 @@
 package org.gradle.build.docs.dsl
 
 import org.w3c.dom.Element
+import org.w3c.dom.Node
 
 class ClassDoc {
     final Element classSection
@@ -9,17 +10,20 @@ class ClassDoc {
     final String classSimpleName
     final ClassMetaData classMetaData
 
-    ClassDoc(String className, Element classSection, ClassMetaData classMetaData, ExtensionMetaData extensionMetaData, DslModel model) {
-        this.classSection = classSection
+    ClassDoc(String className, Element classContent, ClassMetaData classMetaData, ExtensionMetaData extensionMetaData, DslModel model) {
         this.className = className
-        id = "dsl:$className"
+        id = className
         classSimpleName = className.tokenize('.').last()
         this.classMetaData = classMetaData
 
+        classSection = classContent.ownerDocument.createElement('chapter')
         classSection['@id'] = id
         classSection.addFirst {
             title(classSimpleName)
         }
+        classContent.childNodes.each { Node n ->
+            classSection << n
+        }
 
         propertiesTable.tr.each { Element tr ->
             def cells = tr.td
diff --git a/gradle.properties b/gradle.properties
index d43770a..f426a85 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,2 @@
-#properties
-#Mon Nov 16 07:56:40 EST 2009
-previousVersion=0.9-rc-1
-nextVersion=0.9-rc-2
+previousVersion=0.9-rc-2
+nextVersion=0.9-rc-3
diff --git a/src/toplevel/changelog.txt b/src/toplevel/changelog.txt
index eb4190b..db082b9 100644
--- a/src/toplevel/changelog.txt
+++ b/src/toplevel/changelog.txt
@@ -1,4 +1,4 @@
 
-Release Notes - Gradle - Version 0.9-rc-1
+Release Notes - Gradle - Version 0.9-rc-3
 
 See http://docs.codehaus.org/display/GRADLE/Gradle+0.9+Release+Notes
\ No newline at end of file
diff --git a/subprojects/gradle-code-quality/code-quality.gradle b/subprojects/gradle-code-quality/code-quality.gradle
index a0b5d69..6b4f755 100644
--- a/subprojects/gradle-code-quality/code-quality.gradle
+++ b/subprojects/gradle-code-quality/code-quality.gradle
@@ -19,7 +19,7 @@ dependencies {
     compile project(':core')
     compile project(':plugins')
 
-    compile "org.codenarc:CodeNarc:0.10 at jar"
+    compile "org.codenarc:CodeNarc:0.11 at jar"
     compile libraries.slf4j_api
 
     // CodeNarc dependencies
@@ -27,7 +27,7 @@ dependencies {
             "org.gmetrics:GMetrics:0.3 at jar"
 
     // Checkstyle dependencies
-    runtime "com.puppycrawl.tools:checkstyle:5.2 at jar",
+    runtime "com.puppycrawl.tools:checkstyle:5.3 at jar",
             libraries.google_collections,
             libraries.antlr,
             "commons-beanutils:commons-beanutils-core:1.8.3 at jar"
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BackwardsCompatibilityIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BackwardsCompatibilityIntegrationTest.groovy
deleted file mode 100644
index 0bcbc39..0000000
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BackwardsCompatibilityIntegrationTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests
-
-import org.junit.runner.RunWith
-import org.junit.Test
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
-import org.gradle.integtests.fixtures.GradleDistribution
-
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.GradleExecuter
-
- at RunWith(DistributionIntegrationTestRunner.class)
-class BackwardsCompatibilityIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
-    private final GradleExecuter gradle08 = dist.previousVersion('0.8')
-    private final GradleExecuter gradle09rc1 = dist.previousVersion('0.9-rc-1')
-
-    @Test
-    public void canBuildJavaProject() {
-        dist.testFile('buildSrc/src/main/groovy').assertIsDir()
-
-        // Upgrade and downgrade
-        eachVersion([gradle08, gradle09rc1, executer, gradle09rc1, gradle08]) { version ->
-            version.inDirectory(dist.testDir).withTasks('build').run()
-        }
-    }
-
-    def eachVersion(Iterable<GradleExecuter> versions, Closure cl) {
-        versions.each { version ->
-            try {
-                System.out.println("building using $version");
-                cl.call(version)
-            } catch (Throwable t) {
-                throw new RuntimeException("Could not build test project using $version.", t)
-            }
-        }
-    }
-}
-
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest.groovy
new file mode 100644
index 0000000..5330515
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests
+
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.Jvm
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class CrossVersionCompatibilityIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final TestResources resources = new TestResources()
+    private final BasicGradleDistribution gradle08 = dist.previousVersion('0.8')
+    private final BasicGradleDistribution gradle09rc1 = dist.previousVersion('0.9-rc-1')
+    private final BasicGradleDistribution gradle09rc2 = dist.previousVersion('0.9-rc-2')
+
+    @Test
+    public void canBuildJavaProject() {
+        dist.testFile('buildSrc/src/main/groovy').assertIsDir()
+
+        // Upgrade and downgrade
+        eachVersion([gradle08, gradle09rc1, gradle09rc2, dist, gradle09rc2, gradle09rc1, gradle08]) { version ->
+            version.executer().inDirectory(dist.testDir).withTasks('build').run()
+        }
+    }
+
+    @Test
+    public void canUseWrapperFromPreviousVersionToRunCurrentVersion() {
+        eachVersion([gradle09rc1, gradle09rc2]) { version ->
+            checkWrapperWorksWith(version, dist)
+        }
+    }
+
+    @Test
+    public void canUseWrapperFromCurrentVersionToRunPreviousVersion() {
+        eachVersion([gradle09rc1, gradle09rc2]) { version ->
+            checkWrapperWorksWith(dist, version)
+        }
+    }
+
+    def checkWrapperWorksWith(BasicGradleDistribution wrapperGenVersion, BasicGradleDistribution executionVersion) {
+        wrapperGenVersion.executer().withTasks('wrapper').withArguments("-PdistZip=$executionVersion.binDistribution.absolutePath", "-PdistVersion=$executionVersion.version").run()
+        def result = wrapperGenVersion.executer().usingExecutable('gradlew').withTasks('hello').run()
+        assert result.output.contains("hello from $executionVersion.version")
+    }
+
+    def eachVersion(Iterable<BasicGradleDistribution> versions, Closure cl) {
+        versions.each { version ->
+            if (!version.worksWith(Jvm.current())) {
+                System.out.println("skipping $version as it does not work with ${Jvm.current()}.")
+                return
+            }
+            try {
+                System.out.println("building using $version");
+                cl.call(version)
+            } catch (Throwable t) {
+                throw new RuntimeException("Could not build test project using $version.", t)
+            }
+        }
+    }
+}
+
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
index 8fbe9a9..c392f0f 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
@@ -149,13 +149,20 @@ class DynamicObjectIntegrationTest {
             javaTask.custom = 'another value'
             groovyTask.custom = 'another value'
             assert !project.hasProperty('custom')
+            assert defaultTask.hasProperty('custom')
             assert defaultTask.custom == 'another value'
             assert defaultTask.doStuff() == 'method'
             assert javaTask.doStuff() == 'method'
             assert groovyTask.doStuff() == 'method'
+            assert configurations.test.hasProperty('custom')
             assert configurations.test.custom == 'value'
             assert configurations.test.doStuff() == 'method'
-            configurations.test.dependencies.each { assert it.custom == 'value' }
+            configurations.test.dependencies.each {
+                assert it.hasProperty('custom')
+                assert it.custom == 'value'
+                assert it.getProperty('custom') == 'value'
+            }
+            assert repositories.hasProperty('custom')
             assert repositories.custom == 'repository'
             assert repositories.doStuff() == 'method'
             repositories {
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy
index f0d8f42..f17b3b2 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy
@@ -17,16 +17,17 @@
 
 package org.gradle.integtests
 
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
+import org.custommonkey.xmlunit.XMLAssert
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TestFile
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.gradle.util.TestFile
-import org.custommonkey.xmlunit.Diff
-import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
-import org.custommonkey.xmlunit.XMLAssert
+import junit.framework.AssertionFailedError
 
 @RunWith(DistributionIntegrationTestRunner)
 class IdeaIntegrationTest {
@@ -72,7 +73,13 @@ class IdeaIntegrationTest {
         assertHasExpectedContents('root/root.ipr')
         assertHasExpectedContents('root/root.iml')
         assertHasExpectedContents('top-level.iml')
-//        assertHasExpectedContents('a child project/a child.iml')
+    }
+
+    @Test
+    public void mergesModuleDependenciesIntoExistingDependencies() {
+        executer.withTasks('idea').run()
+
+        assertHasExpectedContents('root.iml')
     }
 
     def assertHasExpectedContents(String path) {
@@ -85,6 +92,10 @@ class IdeaIntegrationTest {
 
         Diff diff = new Diff(expectedXml, file.text)
         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
-        XMLAssert.assertXMLEqual(diff, true);
+        try {
+            XMLAssert.assertXMLEqual(diff, true)
+        } catch (AssertionFailedError e) {
+            throw new AssertionFailedError("generated file '$path' does not contain the expected contents: ${e.message}.\nExpected:\n${expectedXml}\nActual:\n${file.text}").initCause(e)
+        }
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
index 3367bf8..43ec6cd 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -25,6 +25,7 @@ import org.junit.Test
 import org.junit.runner.RunWith
 import static org.junit.Assert.*
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.GradleVersion
 
 /**
  * @author Hans Dockter
@@ -55,5 +56,6 @@ class OsgiProjectSampleIntegrationTest {
         assertTrue(start <= Long.parseLong(manifest.mainAttributes.getValue('Bnd-LastModified')))
         assertEquals('1.0', manifest.mainAttributes.getValue('Bundle-Version'))
         assertEquals('gradle_tooling.osgi', manifest.mainAttributes.getValue('Bundle-SymbolicName'))
+        assertEquals( new GradleVersion().version, manifest.mainAttributes.getValue('Built-By'))
     }
 }
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
index 8c34704..5312469 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
@@ -13,19 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.integtests
 
 import groovy.io.PlatformLineWriter
 import junit.framework.AssertionFailedError
 import org.apache.tools.ant.taskdefs.Delete
-import org.gradle.StartParameter
 import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.GradleDistributionExecuter.StartParameterModifier
 import org.gradle.util.AntUtil
 import org.junit.Assert
 import org.junit.runner.Description
@@ -91,9 +86,6 @@ class UserGuideSamplesRunner extends Runner {
             println("Test Id: $run.id, dir: $run.subDir, args: $run.args")
             File rootProjectDir = dist.samplesDir.file(run.subDir)
             executer.inDirectory(rootProjectDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
-            if (executer instanceof GradleDistributionExecuter) {
-                ((GradleDistributionExecuter) executer).setInProcessStartParameterModifier(createModifier(rootProjectDir))
-            }
 
             ExecutionResult result = run.expectFailure ? executer.runWithFailure() : executer.run()
             if (run.outputFile) {
@@ -176,34 +168,6 @@ class UserGuideSamplesRunner extends Runner {
         return actual == expected
     }
 
-    static GradleDistributionExecuter.StartParameterModifier createModifier(File rootProjectDir) {
-        {StartParameter parameter ->
-            if (parameter.getCurrentDir() != null) {
-                parameter.setCurrentDir(normalizedPath(parameter.getCurrentDir(), rootProjectDir));
-            }
-            if (parameter.getBuildFile() != null) {
-                parameter.setBuildFile(normalizedPath(parameter.getBuildFile(), rootProjectDir));
-            }
-            List<File> initScripts = new ArrayList<File>();
-            for (File initScript: parameter.getInitScripts()) {
-                initScripts.add(normalizedPath(initScript, rootProjectDir));
-            }
-            parameter.setInitScripts(initScripts);
-        } as StartParameterModifier
-    }
-
-    static File normalizedPath(File path, File rootProjectDir) {
-        String pathName = path.getAbsolutePath();
-        if (!pathName.startsWith(rootProjectDir.getAbsolutePath())) {
-            String currentDirName = new File("").getAbsolutePath();
-            if (!pathName.startsWith(currentDirName)) {
-                throw new RuntimeException("Path " + path + " is neither subdir of Gradle home nor of the root project!.")
-            }
-            pathName = new File(rootProjectDir, pathName.substring(currentDirName.length() + 1)).getAbsolutePath();
-        }
-        return new File(pathName);
-    }
-
     static Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
         Node samples = new XmlParser().parse(new File(userguideInfoDir, 'samples.xml'))
         Map<String, List<GradleRun>> samplesByDir = new LinkedHashMap<String, List<GradleRun>>()
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
index a2d8d88..d45fad0 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.util.Jvm;
+
 import java.io.File;
 import java.util.*;
 
@@ -42,6 +44,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
+    public boolean worksWith(Jvm jvm) {
+        return jvm.isJava5Compatible();
+    }
+
     public GradleExecuter inDirectory(File directory) {
         workingDir = directory;
         return this;
@@ -80,6 +86,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         throw new UnsupportedOperationException();
     }
 
+    public File getUserHomeDir() {
+        return userHomeDir;
+    }
+
     public GradleExecuter withUserHomeDir(File userHomeDir) {
         this.userHomeDir = userHomeDir;
         return this;
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
index 4c8646f..f2e043e 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
@@ -15,10 +15,32 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.util.Jvm;
 import org.gradle.util.TestFile;
 
 public interface BasicGradleDistribution {
+    /**
+     * Returns the root directory of the installed distribution
+     */
     TestFile getGradleHomeDir();
 
+    /**
+     * Returns the binary distribution.
+     */
+    TestFile getBinDistribution();
+
+    /**
+     * Returns the version of this distribution.
+     */
     String getVersion();
+
+    /**
+     * Creates an executer which will use this distribution.
+     */
+    GradleExecuter executer();
+
+    /**
+     * Returns true if this distribution supports the given JVM.
+     */
+    boolean worksWith(Jvm jvm);
 }
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
new file mode 100644
index 0000000..ae43922
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures;
+
+import org.gradle.util.TestFile;
+
+import java.io.File;
+import java.util.*;
+
+public class DaemonGradleExecuter extends ForkingGradleExecuter {
+    private static final Set<File> DAEMONS = new HashSet<File>();
+
+    public DaemonGradleExecuter(TestFile gradleHomeDir) {
+        super(gradleHomeDir);
+    }
+
+    @Override
+    protected Map doRun(boolean expectFailure) {
+        addShutdownHook(getUserHomeDir());
+        Map result = super.doRun(expectFailure);
+        String output = (String) result.get("output");
+        output = output.replace(String.format("Note: the Gradle build daemon is an experimental feature.%n"), "");
+        output = output.replace(String.format("As such, you may experience unexpected build failures. You may need to occasionally stop the daemon.%n"), "");
+        result.put("output", output);
+        return result;
+    }
+
+    private void addShutdownHook(final File userHomeDir) {
+        if (!DAEMONS.add(userHomeDir)) {
+            return;
+        }
+        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+            public void run() {
+                new ForkingGradleExecuter(getGradleHomeDir()).withUserHomeDir(userHomeDir).withArguments("--stop").run();
+            }
+        }));
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> args = new ArrayList<String>();
+        args.add("--daemon");
+        args.addAll(super.getAllArgs());
+        return args;
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
index cfd036b..1a2b7f8 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
@@ -46,6 +46,10 @@ public class ForkingGradleExecuter extends AbstractGradleExecuter {
         this.gradleHomeDir = gradleHomeDir;
     }
 
+    public TestFile getGradleHomeDir() {
+        return gradleHomeDir;
+    }
+
     @Override
     protected ExecutionResult doRun() {
         Map result = doRun(false);
@@ -58,7 +62,7 @@ public class ForkingGradleExecuter extends AbstractGradleExecuter {
         return new ForkedExecutionFailure(result);
     }
 
-    private Map doRun(boolean expectFailure) {
+    protected Map doRun(boolean expectFailure) {
         gradleHomeDir.assertIsDir();
 
         CommandBuilder commandBuilder = OperatingSystem.current().isWindows() ? new WindowsCommandBuilder()
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
index cf3180d..bd4425a 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
@@ -16,10 +16,7 @@
 
 package org.gradle.integtests.fixtures;
 
-import org.gradle.util.GradleVersion;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
-import org.gradle.util.TestFileContext;
+import org.gradle.util.*;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.Statement;
@@ -54,6 +51,10 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
         this.userHome = USER_HOME_DIR;
     }
 
+    public boolean worksWith(Jvm jvm) {
+        return jvm.isJava5Compatible();
+    }
+
     public void requireOwnUserHomeDir() {
         userHome = getTestDir().file("user-home");
     }
@@ -90,6 +91,10 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
         return new GradleVersion().getVersion();
     }
 
+    public TestFile getBinDistribution() {
+        return getDistributionsDir().file(String.format("gradle-%s-bin.zip", getVersion()));
+    }
+
     /**
      * The samples from the distribution. These are usually shared with other tests.
      */
@@ -134,15 +139,19 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
     }
 
     /**
-     * Returns an executer which can execute a previous version of Gradle.
+     * Returns a previous version of Gradle.
      *
      * @param version The Gradle version
      * @return An executer
      */
-    public PreviousGradleVersionExecuter previousVersion(String version) {
+    public BasicGradleDistribution previousVersion(String version) {
         return new PreviousGradleVersionExecuter(this, version);
     }
 
+    public GradleExecuter executer() {
+        return new GradleDistributionExecuter(this);
+    }
+
     /**
      * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
      */
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
index c8077e2..1cd7e19 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
@@ -29,19 +29,22 @@ import java.io.File;
  * GradleDistribution}. If not supplied in the constructor, this rule locates a field on the test object with type
  * {@link GradleDistribution}.
  *
- * By default, this executer will execute Gradle in a forked process. There is a system property which enables
- * executing Gradle in the current process.
+ * By default, this executer will execute Gradle in a forked process. There is a system property which enables executing
+ * Gradle in the current process.
  */
 public class GradleDistributionExecuter extends AbstractGradleExecuter implements MethodRule {
-    private static final String FORK_SYS_PROP = "org.gradle.integtest.fork";
-    private static final boolean FORK;
+    private static final String EXECUTER_SYS_PROP = "org.gradle.integtest.executer";
+    private static final Executer EXECUTER;
     private GradleDistribution dist;
-    private StartParameterModifier inProcessStartParameterModifier;
     private boolean workingDirSet;
     private boolean userHomeSet;
 
+    private enum Executer {
+        forking, embedded, daemon
+    }
+
     static {
-        FORK = !System.getProperty(FORK_SYS_PROP, "true").equalsIgnoreCase("false");
+        EXECUTER = Executer.valueOf(System.getProperty(EXECUTER_SYS_PROP, Executer.forking.toString()).toLowerCase());
     }
 
     public GradleDistributionExecuter(GradleDistribution dist) {
@@ -98,10 +101,6 @@ public class GradleDistributionExecuter extends AbstractGradleExecuter implement
         return result;
     }
 
-    public void setInProcessStartParameterModifier(StartParameterModifier inProcessStartParameterModifier) {
-        this.inProcessStartParameterModifier = inProcessStartParameterModifier;
-    }
-
     private GradleExecuter configureExecuter() {
         if (!workingDirSet) {
             inDirectory(dist.getTestDir());
@@ -113,7 +112,7 @@ public class GradleDistributionExecuter extends AbstractGradleExecuter implement
         if (!getClass().desiredAssertionStatus()) {
             throw new RuntimeException("Assertions must be enabled when running integration tests.");
         }
-        
+
         StartParameter parameter = new StartParameter();
         parameter.setLogLevel(LogLevel.INFO);
         parameter.setSearchUpwards(false);
@@ -123,14 +122,10 @@ public class GradleDistributionExecuter extends AbstractGradleExecuter implement
 
         GradleExecuter returnedExecuter = inProcessGradleExecuter;
 
-        if (FORK || !inProcessGradleExecuter.canExecute()) {
-            ForkingGradleExecuter forkingGradleExecuter = new ForkingGradleExecuter(dist.getGradleHomeDir());
+        if (EXECUTER != Executer.embedded || !inProcessGradleExecuter.canExecute()) {
+            ForkingGradleExecuter forkingGradleExecuter = EXECUTER == Executer.daemon ? new DaemonGradleExecuter(dist.getGradleHomeDir()) : new ForkingGradleExecuter(dist.getGradleHomeDir());
             copyTo(forkingGradleExecuter);
             returnedExecuter = forkingGradleExecuter;
-        } else {
-            if (inProcessStartParameterModifier != null) {
-                inProcessStartParameterModifier.modify(inProcessGradleExecuter.getParameter());
-            }
         }
 
         boolean settingsFound = false;
@@ -147,8 +142,4 @@ public class GradleDistributionExecuter extends AbstractGradleExecuter implement
 
         return returnedExecuter;
     }
-
-    public static interface StartParameterModifier {
-        void modify(StartParameter startParameter);
-    }
 }
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
index 8d32e88..4bfe9ac 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.util.Jvm;
+
 import java.io.File;
 import java.util.List;
 import java.util.Map;
@@ -84,4 +86,6 @@ public interface GradleExecuter {
      * @return The result.
      */
     ExecutionFailure runWithFailure();
+
+    boolean worksWith(Jvm jvm);
 }
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
index 2b4982b..d693a7b 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.integtests.fixtures
 
+import org.gradle.util.Jvm
 import org.gradle.util.TestFile
 
 public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implements BasicGradleDistribution {
@@ -30,12 +31,37 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         "Gradle $version"
     }
 
+    @Override
+    boolean worksWith(Jvm jvm) {
+        return version == '0.9-rc-1' ? jvm.isJava6Compatible() : jvm.isJava5Compatible()
+    }
+
     protected ExecutionResult doRun() {
         ForkingGradleExecuter executer = new ForkingGradleExecuter(gradleHomeDir)
+        executer.inDirectory(dist.testDir)
         copyTo(executer)
         return executer.run()
     }
 
+    GradleExecuter executer() {
+        this
+    }
+
+    TestFile getBinDistribution() {
+        def zipFile = dist.userHomeDir.parentFile.file("gradle-$version-bin.zip")
+        if (!zipFile.isFile()) {
+            try {
+                URL url = new URL("http://dist.codehaus.org/gradle/${zipFile.name}")
+                System.out.println("downloading $url");
+                zipFile.copyFrom(url)
+            } catch (Throwable t) {
+                zipFile.delete()
+                throw t
+            }
+        }
+        return zipFile
+    }
+
     def TestFile getGradleHomeDir() {
         return findGradleHome()
     }
@@ -46,17 +72,7 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         TestFile gradleHome = versionsDir.file("gradle-$version")
         TestFile markerFile = gradleHome.file('ok.txt')
         if (!markerFile.isFile()) {
-            TestFile zipFile = dist.userHomeDir.parentFile.file("gradle-$version-bin.zip")
-            if (!zipFile.isFile()) {
-                try {
-                    URL url = new URL("http://dist.codehaus.org/gradle/${zipFile.name}")
-                    System.out.println("downloading $url");
-                    zipFile.copyFrom(url)
-                } catch (Throwable t) {
-                    zipFile.delete()
-                    throw t
-                }
-            }
+            TestFile zipFile = binDistribution
             zipFile.usingNativeTools().unzipTo(versionsDir)
             markerFile.touch()
         }
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
index 30e1722..c3586f3 100644
--- a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
@@ -21,11 +21,13 @@ import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.gradle.integtests.DistributionIntegrationTestRunner
+import org.gradle.integtests.fixtures.TestResources
 
 @RunWith(DistributionIntegrationTestRunner.class)
 class MavenProjectIntegrationTest {
     @Rule public final GradleDistribution dist = new GradleDistribution()
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources testResources = new TestResources()
 
     @Test
     public void handlesSubProjectsWithoutTheMavenPluginApplied() {
@@ -39,49 +41,26 @@ class MavenProjectIntegrationTest {
 
     @Test
     public void canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration() {
-        dist.testFile("build.gradle") << '''
-            apply plugin: 'java'
-            apply plugin: 'maven'
-            group = 'root'
-            repositories { mavenCentral() }
-            configurations { custom }
-            dependencies {
-                custom 'commons-collections:commons-collections:3.2'
-                runtime 'commons-collections:commons-collections:3.2'
-            }
-            uploadArchives {
-                repositories {
-                    mavenDeployer {
-                        repository(url: "file://localhost/$projectDir/pomRepo/")
-                    }
-                }
-            }
-        '''
         executer.withTasks('uploadArchives').run()
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsDeployed('root-1.0.jar')
     }
-    
+
     @Test
     public void canDeployAProjectWithNoMainArtifact() {
-        def file = dist.testFile("build.gradle") << '''
-            apply plugin: 'java'
-            apply plugin: 'maven'
-            group = 'root'
-            jar.enabled = false
-            task sourceJar(type: Jar) {
-                classifier = 'source'
-            }
-            artifacts {
-                archives sourceJar
-            }
-            uploadArchives {
-                repositories {
-                    mavenDeployer {
-                        repository(url: "file://localhost/$projectDir/pomRepo/")
-                    }
-                }
-            }
-        '''
-        println file.absolutePath
         executer.withTasks('uploadArchives').run()
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsDeployed('root-1.0-source.jar')
+    }
+
+    @Test
+    public void canDeployAProjectWithMetadataArtifacts() {
+        executer.withTasks('uploadArchives').run()
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsDeployed('root-1.0.jar', 'root-1.0.jar.sig', 'root-1.0.pom', 'root-1.0.pom.sig')
+    }
+
+    def MavenRepository repo() {
+        new MavenRepository(dist.testFile('mavenRepo'))
     }
 }
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenRepository.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenRepository.groovy
new file mode 100644
index 0000000..b036e34
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenRepository.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.maven
+
+import org.gradle.util.TestFile
+
+class MavenRepository {
+    final TestFile rootDir
+
+    MavenRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    MavenModule module(String group, String artifactId, Object version) {
+        TestFile moduleDir = rootDir.file(group, artifactId, version)
+        moduleDir.assertIsDir()
+        return new MavenModule(moduleDir)
+    }
+}
+
+class MavenModule {
+    final TestFile moduleDir
+
+    MavenModule(TestFile moduleDir) {
+        this.moduleDir = moduleDir
+    }
+
+    void assertArtifactsDeployed(String... names) {
+        for (name in names) {
+            moduleDir.file(name).assertIsFile()
+            moduleDir.file("${name}.md5").assertIsFile()
+            moduleDir.file("${name}.sha1").assertIsFile()
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
similarity index 100%
rename from subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
rename to subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
similarity index 100%
rename from subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
rename to subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
similarity index 100%
rename from subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
rename to subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/shared/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..667d839
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
@@ -0,0 +1,11 @@
+
+task wrapper(type: Wrapper) {
+    doFirst {
+        gradleVersion = distVersion
+        urlRoot = new File(project.distZip).parentFile.toURI().toString()
+    }
+}
+
+task hello {
+    doLast { println "hello from $gradle.gradleVersion" }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
index f3208d8..7fd6260 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
@@ -1,5 +1,8 @@
+apply plugin: 'java'
+
 dependencies {
     runtime 'commons-collections:commons-collections:3.2 at jar'
+    testCompile 'junit:junit:4.7'
 }
 
 sourceSets {
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
index 4ff1df4..8379abf 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
@@ -1,11 +1,11 @@
 <classpath>
-  <classpathentry kind="output" path="bin"/>
+  <classpathentry kind="output" path="build/classes/main"/>
   <classpathentry output="build/classes/integTest" kind="src" path="src/integTest/java"/>
   <classpathentry output="build/classes/main" kind="src" path="src/main/resources"/>
   <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
   <classpathentry output="build/classes/test" kind="src" path="src/test/resources"/>
   <classpathentry output="build/classes/test" kind="src" path="src/test/java"/>
   <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-  <classpathentry sourcepath="GRADLE_CACHE/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar" kind="var" path="GRADLE_CACHE/commons-collections/commons-collections/jars/commons-collections-3.2.jar" exported="true"/>
-  <classpathentry sourcepath="GRADLE_CACHE/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_CACHE/junit/junit/jars/junit-4.7.jar" exported="true"/>
+  <classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar" kind="lib" path="@CACHE_DIR@/commons-collections/commons-collections/jars/commons-collections-3.2.jar" exported="true"/>
+  <classpathentry sourcepath="@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/jars/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiJdt.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiJdt.properties
new file mode 100644
index 0000000..416f4fb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiJdt.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
index d857ce5..0cd7c47 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
@@ -1,11 +1,11 @@
 <classpath>
-  <classpathentry kind="output" path="bin"/>
+  <classpathentry kind="output" path="build/classes/main"/>
   <classpathentry output="build/classes/main" kind="src" path="src/main/resources"/>
   <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
   <classpathentry output="build/classes/main" kind="src" path="src/main/groovy"/>
   <classpathentry output="build/classes/test" kind="src" path="src/test/resources"/>
   <classpathentry output="build/classes/test" kind="src" path="src/test/java"/>
   <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
-  <classpathentry sourcepath="GRADLE_CACHE/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar" kind="var" path="GRADLE_CACHE/commons-collections/commons-collections/jars/commons-collections-3.2.jar" exported="true"/>
-  <classpathentry sourcepath="GRADLE_CACHE/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_CACHE/junit/junit/jars/junit-4.7.jar" exported="true"/>
+  <classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar" kind="lib" path="@CACHE_DIR@/commons-collections/commons-collections/jars/commons-collections-3.2.jar" exported="true"/>
+  <classpathentry sourcepath="@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/jars/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectJdt.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectJdt.properties
new file mode 100644
index 0000000..416f4fb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectJdt.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml
new file mode 100644
index 0000000..16ab909
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectClasspath.xml
@@ -0,0 +1,4 @@
+<classpath>
+  <classpathentry kind="output" path="build/eclipse"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectJdt.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectJdt.properties
new file mode 100644
index 0000000..416f4fb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectJdt.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectProject.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectProject.xml
new file mode 100644
index 0000000..35e4b3f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/javabaseprojectProject.xml
@@ -0,0 +1,21 @@
+<projectDescription>
+  <name>
+    javabaseproject
+  </name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>
+      org.eclipse.jdt.core.javanature
+    </nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>
+        org.eclipse.jdt.core.javabuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
new file mode 100644
index 0000000..515fd4a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
@@ -0,0 +1,5 @@
+<classpath>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Jdt.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Jdt.properties
new file mode 100644
index 0000000..8000cd6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Jdt.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Project.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Project.xml
new file mode 100644
index 0000000..5c77ba6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Project.xml
@@ -0,0 +1,39 @@
+<projectDescription>
+  <name>
+    webAppJava6
+  </name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>
+      org.eclipse.jdt.core.javanature
+    </nature>
+    <nature>
+      org.eclipse.wst.common.project.facet.core.nature
+    </nature>
+    <nature>
+      org.eclipse.wst.common.modulecore.ModuleCoreNature
+    </nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>
+        org.eclipse.jdt.core.javabuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>
+        org.eclipse.wst.common.project.facet.core.builder
+      </name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>
+        org.eclipse.wst.validation.validationbuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml
new file mode 100644
index 0000000..ecbfcc9
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpComponent.xml
@@ -0,0 +1,8 @@
+<project-modules id="moduleCoreId" project-version="2.0">
+  <wb-module deploy-name="webAppJava6">
+    <property name="java-output-path" value="build/classes/main"/>
+    <property name="context-root" value="webAppJava6"/>
+    <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
+    <wb-resource deploy-path="/" source-path="src/main/webapp"/>
+  </wb-module>
+</project-modules>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpFacet.xml
similarity index 74%
copy from subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
copy to subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpFacet.xml
index b5d8ac9..f5458de 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6WtpFacet.xml
@@ -2,5 +2,5 @@
   <fixed facet="jst.java"/>
   <fixed facet="jst.web"/>
   <installed facet="jst.web" version="2.4"/>
-  <installed facet="jst.java" version="1.4"/>
+  <installed facet="jst.java" version="6.0"/>
 </faceted-project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
new file mode 100644
index 0000000..530e2ba
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
@@ -0,0 +1,11 @@
+<classpath>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+  <classpathentry sourcepath="GRADLE_USER_HOME/cache/commons-lang/commons-lang/sources/commons-lang-2.5-sources.jar" kind="var" path="GRADLE_USER_HOME/cache/commons-lang/commons-lang/jars/commons-lang-2.5.jar" exported="true">
+    <attributes>
+      <attribute name="javadoc_location" value="GRADLE_USER_HOME/cache/commons-lang/commons-lang/javadocs/commons-lang-2.5-javadoc.jar"/>
+    </attributes>
+  </classpathentry>
+  <classpathentry sourcepath="GRADLE_USER_HOME/cache/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_USER_HOME/cache/junit/junit/jars/junit-4.7.jar" exported="true"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsJdt.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsJdt.properties
new file mode 100644
index 0000000..416f4fb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsJdt.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsProject.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsProject.xml
new file mode 100644
index 0000000..def9ae5
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsProject.xml
@@ -0,0 +1,39 @@
+<projectDescription>
+  <name>
+    webAppWithVars
+  </name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>
+      org.eclipse.jdt.core.javanature
+    </nature>
+    <nature>
+      org.eclipse.wst.common.project.facet.core.nature
+    </nature>
+    <nature>
+      org.eclipse.wst.common.modulecore.ModuleCoreNature
+    </nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>
+        org.eclipse.jdt.core.javabuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>
+        org.eclipse.wst.common.project.facet.core.builder
+      </name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>
+        org.eclipse.wst.validation.validationbuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
similarity index 60%
copy from subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
copy to subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
index 77ad972..4414d7f 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
@@ -1,14 +1,10 @@
 <project-modules id="moduleCoreId" project-version="2.0">
-  <wb-module deploy-name="webservice">
+  <wb-module deploy-name="webAppWithVars">
     <property name="java-output-path" value="build/classes/main"/>
+    <property name="context-root" value="webAppWithVars"/>
     <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
     <wb-resource deploy-path="/" source-path="src/main/webapp"/>
-    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/resource/api/api">
-      <dependency-type>
-        uses
-      </dependency-type>
-    </dependent-module>
-    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/var/GRADLE_CACHE//commons-lang/commons-lang/jars/commons-lang-2.5.jar">
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/var/GRADLE_USER_HOME//cache/commons-lang/commons-lang/jars/commons-lang-2.5.jar">
       <dependency-type>
         uses
       </dependency-type>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpFacet.xml
similarity index 74%
copy from subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
copy to subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpFacet.xml
index b5d8ac9..b5a7e31 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpFacet.xml
@@ -2,5 +2,5 @@
   <fixed facet="jst.java"/>
   <fixed facet="jst.web"/>
   <installed facet="jst.web" version="2.4"/>
-  <installed facet="jst.java" version="1.4"/>
+  <installed facet="jst.java" version="5.0"/>
 </faceted-project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
index a1a77fb..85049f1 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
@@ -1,18 +1,18 @@
 <classpath>
-  <classpathentry kind="output" path="bin"/>
+  <classpathentry kind="output" path="build/classes/main"/>
   <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
   <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
   <classpathentry kind="src" path="/api" exported="true"/>
-  <classpathentry sourcepath="GRADLE_CACHE/commons-lang/commons-lang/sources/commons-lang-2.5-sources.jar" kind="var" path="GRADLE_CACHE/commons-lang/commons-lang/jars/commons-lang-2.5.jar" exported="true">
+  <classpathentry sourcepath="@CACHE_DIR@/commons-lang/commons-lang/sources/commons-lang-2.5-sources.jar" kind="lib" path="@CACHE_DIR@/commons-lang/commons-lang/jars/commons-lang-2.5.jar" exported="true">
     <attributes>
-      <attribute name="javadoc_location" value="GRADLE_CACHE/commons-lang/commons-lang/javadocs/commons-lang-2.5-javadoc.jar"/>
+      <attribute name="javadoc_location" value="@CACHE_DIR@/commons-lang/commons-lang/javadocs/commons-lang-2.5-javadoc.jar"/>
     </attributes>
   </classpathentry>
-  <classpathentry sourcepath="GRADLE_CACHE/commons-io/commons-io/sources/commons-io-1.2-sources.jar" kind="var" path="GRADLE_CACHE/commons-io/commons-io/jars/commons-io-1.2.jar" exported="true">
+  <classpathentry sourcepath="@CACHE_DIR@/commons-io/commons-io/sources/commons-io-1.2-sources.jar" kind="lib" path="@CACHE_DIR@/commons-io/commons-io/jars/commons-io-1.2.jar" exported="true">
     <attributes>
-      <attribute name="javadoc_location" value="GRADLE_CACHE/commons-io/commons-io/javadocs/commons-io-1.2-javadoc.jar"/>
+      <attribute name="javadoc_location" value="@CACHE_DIR@/commons-io/commons-io/javadocs/commons-io-1.2-javadoc.jar"/>
     </attributes>
   </classpathentry>
-  <classpathentry sourcepath="GRADLE_CACHE/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_CACHE/junit/junit/jars/junit-4.7.jar" exported="true"/>
-  <classpathentry sourcepath="GRADLE_CACHE/org.slf4j/slf4j-api/sources/slf4j-api-1.5.8-sources.jar" kind="var" path="GRADLE_CACHE/org.slf4j/slf4j-api/jars/slf4j-api-1.5.8.jar" exported="true"/>
+  <classpathentry sourcepath="@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/jars/junit-4.7.jar" exported="true"/>
+  <classpathentry sourcepath="@CACHE_DIR@/org.slf4j/slf4j-api/sources/slf4j-api-1.5.8-sources.jar" kind="lib" path="@CACHE_DIR@/org.slf4j/slf4j-api/jars/slf4j-api-1.5.8.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceJdt.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceJdt.properties
new file mode 100644
index 0000000..416f4fb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceJdt.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
index 77ad972..5cb8a4e 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
@@ -1,5 +1,6 @@
 <project-modules id="moduleCoreId" project-version="2.0">
   <wb-module deploy-name="webservice">
+    <property name="context-root" value="webservice"/>
     <property name="java-output-path" value="build/classes/main"/>
     <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
     <wb-resource deploy-path="/" source-path="src/main/webapp"/>
@@ -8,7 +9,7 @@
         uses
       </dependency-type>
     </dependent-module>
-    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/var/GRADLE_CACHE//commons-lang/commons-lang/jars/commons-lang-2.5.jar">
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib/@CACHE_DIR@/commons-lang/commons-lang/jars/commons-lang-2.5.jar">
       <dependency-type>
         uses
       </dependency-type>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
index b5d8ac9..b5a7e31 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
@@ -2,5 +2,5 @@
   <fixed facet="jst.java"/>
   <fixed facet="jst.web"/>
   <installed facet="jst.web" version="2.4"/>
-  <installed facet="jst.java" version="1.4"/>
+  <installed facet="jst.java" version="5.0"/>
 </faceted-project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle
index 2e45a1c..2da4a99 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle
@@ -2,4 +2,5 @@ apply plugin: 'groovy'
 
 dependencies {
     runtime 'commons-collections:commons-collections:3.2 at jar'
+    testCompile 'junit:junit:4.7'
 }
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/javabaseproject/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/javabaseproject/build.gradle
new file mode 100644
index 0000000..28a9ce2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/javabaseproject/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'java-base'
+
+sourceSets {
+    custom
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
index 4ca1c5c..484f36a 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
@@ -1,6 +1,8 @@
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
 import org.custommonkey.xmlunit.XMLAssert
+import junit.framework.AssertionFailedError
+import org.junit.ComparisonFailure
 
 buildscript {
     repositories {
@@ -18,48 +20,80 @@ allprojects {
 }
 
 subprojects {
-    apply plugin: 'java'
-
     repositories {
         mavenCentral()
     }
 
-    dependencies {
-        testCompile 'junit:junit:4.7'
-    }
-
     group = 'org.gradle'
     version = '1.0'
+}
+
+allprojects {
+    afterEvaluate { p ->
+        configure(p) {
+            eclipseProject.doLast {
+                compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Project.xml")),
+                        file(".project").text)
+            }
 
-    eclipseClasspath {
-        downloadJavadoc = true
-        doLast {
-            compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Classpath.xml").text,
-                    file(".classpath").text)
+            if (p.hasProperty('eclipseClasspath')) {
+                eclipseClasspath {
+                    downloadJavadoc = true
+                    doLast {
+                        compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Classpath.xml")),
+                                file(".classpath").text)
+                    }
+                }
+            }
+
+            if (p.hasProperty('eclipseJdt')) {
+                eclipseJdt {
+                    doLast {
+                        compareProperties(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Jdt.properties")),
+                                file(".settings/org.eclipse.jdt.core.prefs").text)
+                    }
+                }
+            }
+
+            if (p.hasProperty('eclipseWtp')) {
+                eclipseWtp {
+                    doLast {
+                        compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}WtpComponent.xml")),
+                                file(".settings/org.eclipse.wst.common.component").text)
+                        compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}WtpFacet.xml")),
+                                file(".settings/org.eclipse.wst.common.project.facet.core.xml").text)
+                    }
+                }
+            }
+            cleanEclipse.doLast {
+                assert !file(".classpath").exists()
+                assert !file(".project").exists()
+                assert !file('.settings').exists() || file('.settings').listFiles().length == 0
+            }
         }
     }
 }
 
-allprojects {
-    eclipseProject.doLast {
-        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Project.xml").text,
-                file(".project").text)
-    }
-    cleanEclipse.doLast {
-        assert !file(".classpath").isFile()
-        assert !file(".project").isFile()
-    }
+void compareProperties(String expectedProperties, String actualProperties) {
+    Properties expected = new Properties()
+    expected.load(new ByteArrayInputStream(expectedProperties.bytes))
+    Properties actual = new Properties()
+    actual.load(new ByteArrayInputStream(actualProperties.bytes))
+    assert expected == actual
 }
 
 void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
     Diff diff = new Diff(expectedXml, actualXml)
     diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
-    XMLAssert.assertXMLEqual(diff, true);
+    try {
+        XMLAssert.assertXMLEqual(diff, true)
+    } catch (AssertionFailedError error) {
+        throw new ComparisonFailure("Unexpected content for generated file: ${error.message}", expectedXml, actualXml).initCause(error)
+    }
 }
 
-String getExpectedXml(Project subProject, String filename) {
-    def cache = new File(gradle.gradleUserHomeDir, "/cache")
-    def path = org.gradle.plugins.idea.model.Path.getRelativePath(subProject.projectDir, '$MODULE_DIR$', cache)
-    return rootProject.file("expectedFiles/$filename").text.replace('@CACHE_DIR@', path)
+String getExpectedXml(File file) {
+    def cache = new File(gradle.gradleUserHomeDir, "/cache").absolutePath.replace(File.separator, '/')
+    return file.text.replace('@CACHE_DIR@', cache)
 }
 
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle
index e08e661..fd56bfc 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle
@@ -1 +1 @@
-includeFlat "api", "webservice", "groovyproject"
+includeFlat "api", "webservice", "groovyproject", "javabaseproject", "webAppWithVars", "webAppJava6"
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle
new file mode 100644
index 0000000..f898d2a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/build.gradle
@@ -0,0 +1,3 @@
+apply plugin: 'war'
+
+sourceCompatibility = 6
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/src/main/java/org/gradle/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..6a8d880
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/src/main/java/org/gradle/Person.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public class Person {
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/src/main/webapp/index.html b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/src/main/webapp/index.html
new file mode 100644
index 0000000..c85eebb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppJava6/src/main/webapp/index.html
@@ -0,0 +1 @@
+<p>index page.</p>
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle
new file mode 100644
index 0000000..15a91df
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'war'
+
+dependencies {
+    runtime "commons-lang:commons-lang:2.5"
+    testCompile 'junit:junit:4.7'
+}
+
+eclipseClasspath.variables = ['GRADLE_USER_HOME': gradle.gradleUserHomeDir]
+eclipseWtp.variables = ['GRADLE_USER_HOME': gradle.gradleUserHomeDir]
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/src/main/java/org/gradle/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..6a8d880
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/src/main/java/org/gradle/Person.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public class Person {
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
index c263edc..b51fded 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
@@ -1,3 +1,4 @@
+apply plugin: 'java'
 apply plugin: 'war'
 
 version = '2.5'
@@ -7,19 +8,5 @@ dependencies {
     providedRuntime "commons-io:commons-io:1.2"
     compile project(':api')
     runtime "commons-lang:commons-lang:2.5"
-}
-
-eclipseWtp {
-    doLast {
-        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/webserviceWtpComponent.xml").text,
-                file(".settings/org.eclipse.wst.common.component.xml").text)
-        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/webserviceWtpFacet.xml").text,
-                file(".settings/org.eclipse.wst.common.project.facet.core.xml").text)
-    }
-}
-
-cleanEclipse {
-    doLast {
-        assert !file(".settings").isFile()
-    }
+    testCompile 'junit:junit:4.7'
 }
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/build.gradle
new file mode 100644
index 0000000..bcfd7ff
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    runtime 'commons-collections:commons-collections:3.2 at jar'
+    runtime 'junit:junit:4.7 at jar'
+}
+
+ideaModule {
+    variables['CUSTOM_DIR'] = new File(gradle.gradleUserHomeDir, 'custom')
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/expectedFiles/root.iml.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/expectedFiles/root.iml.xml
new file mode 100644
index 0000000..a50cf87
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/expectedFiles/root.iml.xml
@@ -0,0 +1,39 @@
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output/>
+    <content url="file://$MODULE_DIR$/">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true"/>
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+    </content>
+    <orderEntry type="inheritedJdk"/>
+    <orderEntry type="sourceFolder" forTests="false"/>
+    <output url="file://$MODULE_DIR$/build/classes/main"/>
+    <output-test url="file://$MODULE_DIR$/build/classes/test"/>
+    <orderEntry type="module-library" exported="" scope="RUNTIME">
+      <library>
+        <CLASSES>
+          <root url="jar://$CUSTOM_DIR$/../cache/junit/junit/jars/junit-4.7.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://$CUSTOM_DIR$/../cache/junit/junit/sources/junit-4.7-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" exported="" scope="RUNTIME">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/jars/commons-collections-3.2.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+</module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/root.iml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/root.iml
new file mode 100644
index 0000000..55c6433
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/root.iml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library" scope="RUNTIME">
+      <library>
+        <CLASSES>
+          <root url="jar://$CUSTOM_DIR$/../cache/junit/junit/jars/junit-4.7.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://$CUSTOM_DIR$/../cache/junit/junit/sources/junit-4.7-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+</module>
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/settings.gradle
new file mode 100644
index 0000000..683b273
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/mergesModuleDependenciesIntoExistingDependencies/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
new file mode 100644
index 0000000..8c0b7a7
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = '1.0'
+repositories { mavenCentral() }
+configurations { custom }
+dependencies {
+    custom 'commons-collections:commons-collections:3.2'
+    runtime 'commons-collections:commons-collections:3.2'
+}
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("mavenRepo"))
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
new file mode 100644
index 0000000..683b273
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/build.gradle
new file mode 100644
index 0000000..fa6c551
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = 1.0
+
+task signature {
+    destFile = file("$buildDir/signature.sig")
+    doLast {
+        destFile.text = 'signature'
+    }
+}
+
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+
+artifacts {
+    archives new DefaultPublishArtifact(jar.baseName, "jar.sig", "jar.sig", null, new Date(), signature.destFile, signature)
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("mavenRepo"))
+            beforeDeployment { MavenDeployment deployment ->
+                assert deployment.pomArtifact.file.isFile()
+                assert deployment.pomArtifact.name == 'root'
+                assert deployment.mainArtifact.file == jar.archivePath
+                assert deployment.mainArtifact.name == 'root'
+                assert deployment.artifacts.size() == 3
+                assert deployment.artifacts.contains(deployment.pomArtifact)
+                assert deployment.artifacts.contains(deployment.mainArtifact)
+
+                def pomSignature = file("${buildDir}/pom.sig")
+                pomSignature.text = 'signature'
+                deployment.addArtifact new DefaultPublishArtifact(deployment.pomArtifact.name, "pom.sig", "pom.sig", null, new Date(), pomSignature)
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/settings.gradle
new file mode 100644
index 0000000..683b273
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/build.gradle
new file mode 100644
index 0000000..4ec1f1d
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'base'
+apply plugin: 'maven'
+
+group = 'group'
+version = 1.0
+
+task sourceJar(type: Jar) {
+    classifier = 'source'
+}
+artifacts {
+    archives sourceJar
+}
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri('mavenRepo'))
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/settings.gradle
new file mode 100644
index 0000000..683b273
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt
index a50b717..cd7d38e 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt
@@ -57,6 +57,12 @@
       <artifactId>runtime</artifactId>
       <version>1.0</version>
       <scope>runtime</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>excludeArtifact2</artifactId>
+          <groupId>excludeGroup2</groupId>
+        </exclusion>
+      </exclusions>
     </dependency>
     <dependency>
       <groupId>group5</groupId>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt
index 666c294..64068c1 100644
--- a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt
@@ -49,6 +49,12 @@
       <artifactId>runtime</artifactId>
       <version>1.0</version>
       <scope>runtime</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>excludeArtifact2</artifactId>
+          <groupId>excludeGroup2</groupId>
+        </exclusion>
+      </exclusions>
       <optional>true</optional>
     </dependency>
     <dependency>
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java
index 8b41a59..f5feadc 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java
@@ -20,9 +20,9 @@ import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.LocationAwareException;
 import org.gradle.api.logging.LogLevel;
-import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.configuration.ImplicitTasksConfigurer;
 import org.gradle.execution.TaskSelectionException;
+import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
@@ -40,6 +40,8 @@ import static org.gradle.logging.StyledTextOutput.Style.UserInput;
  * A {@link BuildListener} which reports the build exception, if any.
  */
 public class BuildExceptionReporter extends BuildAdapter {
+    public final BuildClientMetaData clientMetaData;
+
     private enum ExceptionStyle {
         None, Sanitized, Full
     }
@@ -47,9 +49,10 @@ public class BuildExceptionReporter extends BuildAdapter {
     private final StyledTextOutputFactory textOutputFactory;
     private final StartParameter startParameter;
 
-    public BuildExceptionReporter(StyledTextOutputFactory textOutputFactory, StartParameter startParameter) {
+    public BuildExceptionReporter(StyledTextOutputFactory textOutputFactory, StartParameter startParameter, BuildClientMetaData clientMetaData) {
         this.textOutputFactory = textOutputFactory;
         this.startParameter = startParameter;
+        this.clientMetaData = clientMetaData;
     }
 
     public void buildFinished(BuildResult result) {
@@ -153,7 +156,7 @@ public class BuildExceptionReporter extends BuildAdapter {
         details.summary.text("Could not determine which tasks to execute.");
         details.details.text(getMessage(failure));
         details.resolution.text("Run ");
-        new GradleLauncherMetaData().describeCommand(details.resolution.withStyle(UserInput), ImplicitTasksConfigurer.TASKS_TASK);
+        clientMetaData.describeCommand(details.resolution.withStyle(UserInput), ImplicitTasksConfigurer.TASKS_TASK);
         details.resolution.text(" to get a list of available tasks.");
     }
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java
index 38c90d1..075d84d 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java
@@ -22,8 +22,8 @@ import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.logging.Logger;
+import org.gradle.initialization.BuildRequestMetaData;
 import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.util.Clock;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -35,10 +35,10 @@ public class BuildLogger implements BuildListener, TaskExecutionGraphListener {
     private final Logger logger;
     private final List<BuildListener> resultLoggers = new ArrayList<BuildListener>();
 
-    public BuildLogger(Logger logger, StyledTextOutputFactory textOutputFactory, Clock buildTimeClock, StartParameter startParameter) {
+    public BuildLogger(Logger logger, StyledTextOutputFactory textOutputFactory, StartParameter startParameter, BuildRequestMetaData requestMetaData) {
         this.logger = logger;
-        resultLoggers.add(new BuildExceptionReporter(textOutputFactory, startParameter));
-        resultLoggers.add(new BuildResultLogger(textOutputFactory, buildTimeClock));
+        resultLoggers.add(new BuildExceptionReporter(textOutputFactory, startParameter, requestMetaData.getClient()));
+        resultLoggers.add(new BuildResultLogger(textOutputFactory, requestMetaData.getBuildTimeClock()));
     }
 
     public void buildStarted(Gradle gradle) {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java
index 2d31632..7c73cf4 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java
@@ -17,6 +17,7 @@ package org.gradle;
 
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.initialization.GradleLauncherFactory;
 
 /**
  * <p>{@code GradleLauncher} is the main entry point for embedding Gradle. You use this class to manage a Gradle build,
@@ -48,8 +49,8 @@ public abstract class GradleLauncher {
     private static GradleLauncherFactory factory;
 
     /**
-     * <p>Executes the build for this GradleLauncher instance and returns the result. Note that when the build fails,
-     * the exception is available using {@link org.gradle.BuildResult#getFailure()}.</p>
+     * <p>Executes the build for this {@code GradleLauncher} instance and returns the result. Note that when the build
+     * fails, the exception is available using {@link org.gradle.BuildResult#getFailure()}.</p>
      *
      * @return The result. Never returns null.
      */
@@ -59,7 +60,7 @@ public abstract class GradleLauncher {
      * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
      * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object.
      *
-     * @return A BuildResult object. Never returns null.
+     * @return The result. Never returns null.
      */
     public abstract BuildResult getBuildAnalysis();
 
@@ -68,15 +69,15 @@ public abstract class GradleLauncher {
      * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object. Fills the execution plan without running
      * the build. The tasks to be executed tasks are available via {@link org.gradle.api.invocation.Gradle#getTaskGraph()}.
      *
-     * @return A BuildResult object. Never returns null.
+     * @return The result. Never returns null.
      */
     public abstract BuildResult getBuildAndRunAnalysis();
 
     /**
-     * Returns a GradleLauncher instance based on the passed start parameter.
+     * Returns a {@code GradleLauncher} instance based on the passed start parameter.
      *
      * @param startParameter The start parameter object the GradleLauncher instance is initialized with
-     * @return The GradleLauncher. Never returns null.
+     * @return The {@code GradleLauncher}. Never returns null.
      */
     public static GradleLauncher newInstance(final StartParameter startParameter) {
         return getFactory().newInstance(startParameter);
@@ -90,23 +91,21 @@ public abstract class GradleLauncher {
     }
 
     /**
-     * Returns a GradleLauncher instance based on the passed command line syntax arguments. Certain command line
-     * arguments won't have any effect if you choose this method (e.g. -v, -h). If you want to act upon, you better use
-     * {@link #createStartParameter(String...)} in conjunction with {@link #newInstance(String...)}.
+     * Returns a {@code GradleLauncher} instance based on the passed command line syntax arguments.
      *
      * @param commandLineArgs A String array where each element denotes an entry of the Gradle command line syntax
-     * @return The GradleLauncher. Never returns null.
+     * @return The {@code GradleLauncher}. Never returns null.
      */
-    public static GradleLauncher newInstance(final String... commandLineArgs) {
-        return getFactory().newInstance(commandLineArgs);
+    public static GradleLauncher newInstance(String... commandLineArgs) {
+        return newInstance(createStartParameter(commandLineArgs));
     }
 
     /**
-     * Returns a StartParameter object out of command line syntax arguments. Every possible command line option has it
-     * associated field in the StartParameter object.
+     * Returns a {@code StartParameter} object out of command line syntax arguments. Each command line option has an
+     * associated field in the {@code StartParameter} object.
      *
      * @param commandLineArgs A String array where each element denotes an entry of the Gradle command line syntax
-     * @return The GradleLauncher. Never returns null.
+     * @return The {@code GradleLauncher}. Never returns null.
      */
     public static StartParameter createStartParameter(final String... commandLineArgs) {
         return getFactory().createStartParameter(commandLineArgs);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java
index d16f481..96fd009 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java
@@ -65,7 +65,7 @@ public class StartParameter {
     private boolean searchUpwards = true;
     private Map<String, String> projectProperties = new HashMap<String, String>();
     private Map<String, String> systemPropertiesArgs = new HashMap<String, String>();
-    private File gradleUserHomeDir = new File(GUtil.elvis(System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY), DEFAULT_GRADLE_USER_HOME.getAbsolutePath()));
+    private File gradleUserHomeDir;
     private CacheUsage cacheUsage = CacheUsage.ON;
     private ScriptSource buildScriptSource;
     private ScriptSource settingsScriptSource;
@@ -85,6 +85,15 @@ public class StartParameter {
      * command-line with no arguments.
      */
     public StartParameter() {
+        String gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY);
+        if (gradleUserHome == null) {
+            gradleUserHome = System.getenv("GRADLE_USER_HOME");
+            if (gradleUserHome == null) {
+                gradleUserHome = DEFAULT_GRADLE_USER_HOME.getAbsolutePath();
+            }
+        }
+
+        gradleUserHomeDir = GFileUtils.canonicalise(new File(gradleUserHome));
         setCurrentDir(null);
     }
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
index 94aa3ac..038c990 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
@@ -21,17 +21,18 @@ import java.io.File;
 import java.util.Collection;
 
 /**
- * <p>A resolver that can only be used for uploading artifacts to a Maven repository. If you use this resolver for getting
- * dependencies from a Maven repository, an exception is thrown. This resolver support all aspects of Maven deployment,
- * including snapshot deployment and metadata.xml manipulation.</p>
- * <p/>
- * <p>You have to specify at least one repository. Otherwise, if there is only one artifact, usually there is not more to do.
- * If there is more than one artifact you have to decide what to do about this, as a Maven pom can only deal with one artifact.
- * There are two strategies. If you want to deploy only one artifact you have to specify a filter to select this artifact. If you
- * want to deploy more than one artifact, you have to specify filters which select each artifact. Associated with each filter is
- * a separate configurable pom.</p>
+ * <p>A resolver that can only be used for uploading artifacts to a Maven repository. If you use this resolver for
+ * getting dependencies from a Maven repository, an exception is thrown. This resolver support all aspects of Maven
+ * deployment, including snapshot deployment and metadata.xml manipulation.</p>
  *
- * <p>You can create an instance of this type via the {@link org.gradle.api.tasks.Upload#getRepositories()} container</p> 
+ * <p>You have to specify at least one repository. Otherwise, if there is only one artifact, usually there is not more
+ * to do. If there is more than one artifact you have to decide what to do about this, as a Maven pom can only deal with
+ * one artifact. There are two strategies. If you want to deploy only one artifact you have to specify a filter to
+ * select this artifact. If you want to deploy more than one artifact, you have to specify filters which select each
+ * artifact. Associated with each filter is a separate configurable pom.</p>
+ *
+ * <p>You can create an instance of this type via the {@link org.gradle.api.tasks.Upload#getRepositories()}
+ * container</p>
  *
  * @author Hans Dockter
  */
@@ -40,13 +41,13 @@ public interface MavenDeployer extends MavenResolver {
     /**
      * Returns the repository o be used for uploading artifacts.
      *
-     * @see #setRepository(org.apache.maven.artifact.ant.RemoteRepository) 
+     * @see #setRepository(org.apache.maven.artifact.ant.RemoteRepository)
      */
     RemoteRepository getRepository();
 
     /**
-     * Sets the repository to be used for uploading artifacts. If {@link #getRepository()} is not set, this repository is
-     * also used for uploading snapshot artifacts.
+     * Sets the repository to be used for uploading artifacts. If {@link #getRepository()} is not set, this repository
+     * is also used for uploading snapshot artifacts.
      *
      * @param repository The repository to be used
      */
@@ -54,36 +55,34 @@ public interface MavenDeployer extends MavenResolver {
 
     /**
      * Returns the repository o be used for uploading snapshot artifacts.
-     * 
+     *
      * @see #setSnapshotRepository(org.apache.maven.artifact.ant.RemoteRepository)
      */
     RemoteRepository getSnapshotRepository();
 
     /**
-     * Sets the repository to be used for uploading snapshot artifacts. If this is not set, the {@link #getRepository()} is used
-     * for uploading snapshot artifacts.
-     *  
+     * Sets the repository to be used for uploading snapshot artifacts. If this is not set, the {@link #getRepository()}
+     * is used for uploading snapshot artifacts.
+     *
      * @param snapshotRepository The repository to be used
      */
     void setSnapshotRepository(RemoteRepository snapshotRepository);
 
     /**
-     * Out of the box only uploading to the filesysten and via http is supported. If other protocolls should be used, the
-     * appropriate Maven wagon jars have to be passed via this method.
-     * 
-     * @param jars
+     * Out of the box only uploading to the filesysten and via http is supported. If other protocolls should be used,
+     * the appropriate Maven wagon jars have to be passed via this method.
      */
     void addProtocolProviderJars(Collection<File> jars);
 
     /**
-     * Returns whether to assign snapshots a unique version comprised of the timestamp and build number, or to use
-     * the same version each time. Defaults to true.
+     * Returns whether to assign snapshots a unique version comprised of the timestamp and build number, or to use the
+     * same version each time. Defaults to true.
      */
     boolean isUniqueVersion();
 
     /**
-     * Sets whether to assign snapshots a unique version comprised of the timestamp and build number, or to use
-     * the same version each time. Defaults to true.
+     * Sets whether to assign snapshots a unique version comprised of the timestamp and build number, or to use the same
+     * version each time. Defaults to true.
      */
     void setUniqueVersion(boolean uniqueVersion);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
new file mode 100644
index 0000000..89b415a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.artifacts.maven;
+
+import org.gradle.api.artifacts.PublishArtifact;
+
+import java.util.Set;
+
+/**
+ * Represents the artifacts which will be deployed to a maven repository. You can use this interface to modify the set
+ * of artifacts.
+ */
+public interface MavenDeployment {
+    /**
+     * Returns the POM for this deployment.
+     *
+     * @return The POM. Never null.
+     */
+    PublishArtifact getPomArtifact();
+
+    /**
+     * Returns the main artifact for this deployment.
+     *
+     * @return The main artifact. May be null.
+     */
+    PublishArtifact getMainArtifact();
+
+    /**
+     * Returns the artifacts which will be deployed. Includes the POM artifact.
+     *
+     * @return The artifacts. Never null.
+     */
+    Set<PublishArtifact> getArtifacts();
+
+    /**
+     * Adds an additional artifact to this deployment.
+     *
+     * @param artifact The artifact to add.
+     */
+    void addArtifact(PublishArtifact artifact);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
index c6f59fa..12268fe 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
@@ -15,18 +15,38 @@
  */
 package org.gradle.api.artifacts.maven;
 
+import groovy.lang.Closure;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.maven.settings.Settings;
+import org.gradle.api.Action;
 
 /**
  * A {@link org.apache.ivy.plugins.resolver.DependencyResolver} which resolves dependencies from Maven repositories.
- * 
+ *
  * @author Hans Dockter
  */
 public interface MavenResolver extends DependencyResolver, PomFilterContainer {
     /**
-     * Returns a maven settings object. This can be used for example to figure out where the local repository is located.
-     * This property is filled after publishing. Before this property is null.
+     * Returns a maven settings object. This can be used for example to figure out where the local repository is
+     * located. This property is filled after publishing. Before this property is null.
      */
     Settings getSettings();
+
+    /**
+     * Adds an action to be executed immediately before a deployment to this resolver. The action is executed after all
+     * artifacts have been build, including generation of the POM. The action can modify the set of artifacts to be
+     * deployed.
+     *
+     * @param action The action to execute.
+     */
+    void beforeDeployment(Action<? super MavenDeployment> action);
+
+    /**
+     * Adds a closure to be executed immediately before a deployment to this resolver. The closure is passed a {@link
+     * org.gradle.api.artifacts.maven.MavenDeployment} as a parameter. The closure is executed after all artifacts have
+     * been build, including generation of the POM. The closure can modify the set of artifacts to be deployed.
+     *
+     * @param action The closure to execute.
+     */
+    void beforeDeployment(Closure action);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
index 07cd01e..38d6949 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
@@ -365,7 +365,12 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
 
         private void addGetter(Method method, MethodCodeBody body) throws Exception {
             String methodDescriptor = Type.getMethodDescriptor(method);
-            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor,
+            String methodName = method.getName();
+            addGetter(methodName, methodDescriptor, body);
+        }
+
+        private void addGetter(String methodName, String methodDescriptor, MethodCodeBody body) throws Exception {
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor,
                     null, new String[0]);
             methodVisitor.visitCode();
             body.add(methodVisitor);
@@ -398,6 +403,31 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                 }
             });
 
+            // GENERATE public boolean hasProperty(String name) { return getAsDynamicObject().hasProperty(name) }
+
+            String methodDescriptor = Type.getMethodDescriptor(Type.getType(Boolean.TYPE), new Type[]{Type.getType(String.class)});
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "hasProperty", methodDescriptor, null, new String[0]);
+            methodVisitor.visitCode();
+
+            // GENERATE getAsDynamicObject().hasProperty(name);
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            String getAsDynamicObjectDesc = Type.getMethodDescriptor(DynamicObjectAware.class.getDeclaredMethod(
+                    "getAsDynamicObject"));
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(),
+                    "getAsDynamicObject", getAsDynamicObjectDesc);
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+            String getPropertyDesc = Type.getMethodDescriptor(DynamicObject.class.getDeclaredMethod(
+                    "hasProperty", String.class));
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, dynamicObjectType.getInternalName(),
+                    "hasProperty", getPropertyDesc);
+
+            // END
+            methodVisitor.visitInsn(Opcodes.IRETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+
             // GENERATE public void setProperty(String name, Object value) { getAsDynamicObject().setProperty(name, value); }
 
             addSetter(GroovyObject.class.getDeclaredMethod("setProperty", String.class, Object.class),
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
index f860796..ed7bf2d 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
@@ -20,7 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.api.DomainObjectCollection;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
-import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ActionBroadcast;
 
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -73,16 +73,16 @@ public class DefaultDomainObjectContainer<T> extends AbstractDomainObjectCollect
     }
 
     private static class SetStore<S> implements ObjectStore<S> {
-        private final ListenerBroadcast<Action> addActions = new ListenerBroadcast<Action>(Action.class);
-        private final ListenerBroadcast<Action> removeActions = new ListenerBroadcast<Action>(Action.class);
+        private final ActionBroadcast<S> addActions = new ActionBroadcast<S>();
+        private final ActionBroadcast<S> removeActions = new ActionBroadcast<S>();
         private final Map<S, S> objects = new LinkedHashMap<S, S>();
 
         public void add(S object) {
             S oldValue = objects.put(object, object);
             if (oldValue != null) {
-                removeActions.getSource().execute(oldValue);
+                removeActions.execute(oldValue);
             }
-            addActions.getSource().execute(object);
+            addActions.execute(object);
         }
 
         public Collection<? extends S> getAll() {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
index 26356f6..ef2ee24 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
@@ -20,7 +20,7 @@ import groovy.lang.MissingPropertyException;
 import org.gradle.api.*;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
-import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ActionBroadcast;
 import org.gradle.util.ConfigureUtil;
 
 import java.util.*;
@@ -81,6 +81,10 @@ public class DefaultNamedDomainObjectContainer<T> extends AbstractDomainObjectCo
         return store.find(name);
     }
 
+    protected T findByNameWithoutRules(String name) {
+        return store.find(name);
+    }
+
     public NamedDomainObjectCollection<T> matching(Spec<? super T> spec) {
         return classGenerator.newInstance(DefaultNamedDomainObjectContainer.class, type, classGenerator, storeWithSpec(spec));
     }
@@ -189,16 +193,16 @@ public class DefaultNamedDomainObjectContainer<T> extends AbstractDomainObjectCo
     }
 
     private static class MapStore<S> implements NamedObjectStore<S> {
-        private final ListenerBroadcast<Action> addActions = new ListenerBroadcast<Action>(Action.class);
-        private final ListenerBroadcast<Action> removeActions = new ListenerBroadcast<Action>(Action.class);
+        private final ActionBroadcast<S> addActions = new ActionBroadcast<S>();
+        private final ActionBroadcast<S> removeActions = new ActionBroadcast<S>();
         private final Map<String, S> objects = new TreeMap<String, S>();
 
         public S put(String name, S value) {
             S oldValue = objects.put(name, value);
             if (oldValue != null) {
-                removeActions.getSource().execute(oldValue);
+                removeActions.execute(oldValue);
             }
-            addActions.getSource().execute(value);
+            addActions.execute(value);
             return oldValue;
         }
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
index 74cbe3b..baa7244 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
@@ -96,6 +96,7 @@ public class GroovySourceGenerationBackedClassGenerator extends AbstractClassGen
         }
 
         public void addDynamicMethods() {
+            src.format("def hasProperty(String name) { getAsDynamicObject().hasProperty(name); }%n");
             src.format("void setProperty(String name, Object value) { getAsDynamicObject().setProperty(name, value); }%n");
             src.format("def propertyMissing(String name) { getAsDynamicObject().getProperty(name); }%n");
             src.format("def methodMissing(String name, Object params) { getAsDynamicObject().invokeMethod(name, (Object[])params); }%n");
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
index 15846f8..a426804 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
@@ -37,4 +37,9 @@ public abstract class AbstractPublishArtifact implements PublishArtifact {
     public void setTaskDependency(TaskDependency taskDependency) {
         this.taskDependency = taskDependency;
     }
+
+    @Override
+    public String toString() {
+        return String.format("%s %s:%s:%s:%s", getClass().getSimpleName(), getName(), getType(), getExtension(), getClassifier());
+    }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
index e8d960e..b83b7a5 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
@@ -63,10 +63,6 @@ public class ArchivePublishArtifact extends AbstractPublishArtifact {
         return GUtil.elvis(date, new Date(archiveTask.getArchivePath().lastModified()));
     }
 
-    public String toString() {
-        return String.format("ArchivePublishArtifact $s:%s:%s:%s", getName(), getType(), getExtension(), getClassifier());
-    }
-
     public AbstractArchiveTask getArchiveTask() {
         return archiveTask;
     }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
index 237ee3e..2707778 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
@@ -41,10 +41,6 @@ public class DefaultPublishArtifact extends AbstractPublishArtifact {
         this.file = file;
     }
 
-    public String toString() {
-        return String.format("DefaultPublishArtifact %s:%s:%s:%s", name, type, extension, classifier);
-    }
-
     public String getName() {
         return name;
     }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
index 3350c3b..40b6e3e 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
@@ -31,7 +31,7 @@ import org.gradle.api.internal.XmlTransformer;
 import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter;
 import org.gradle.api.internal.artifacts.publish.maven.pombuilder.CustomModelBuilder;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ActionBroadcast;
 
 import java.io.*;
 import java.util.Collections;
@@ -45,7 +45,7 @@ public class DefaultMavenPom implements MavenPom {
     private FileResolver fileResolver;
     private MavenProject mavenProject = new MavenProject();
     private Conf2ScopeMappingContainer scopeMappings;
-    private ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    private ActionBroadcast<MavenPom> whenConfiguredActions = new ActionBroadcast<MavenPom>();
     private XmlTransformer withXmlActions = new XmlTransformer();
     private ConfigurationContainer configurations;
 
@@ -165,7 +165,7 @@ public class DefaultMavenPom implements MavenPom {
         }
         effectivePom.getDependencies().addAll(getGeneratedDependencies());
         effectivePom.withXmlActions = withXmlActions;
-        whenConfiguredActions.getSource().execute(effectivePom);
+        whenConfiguredActions.execute(effectivePom);
         return effectivePom;
     }
 
@@ -217,7 +217,7 @@ public class DefaultMavenPom implements MavenPom {
     }
 
     public DefaultMavenPom whenConfigured(final Closure closure) {
-        whenConfiguredActions.add("execute", closure);
+        whenConfiguredActions.add(closure);
         return this;
     }
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java
index 29e2347..2659d09 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java
@@ -38,20 +38,21 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
     }
 
     public List<org.apache.maven.model.Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations) {
-        Map<ModuleDependency, String> dependenciesMap = createDependencyToScopeMap(conf2ScopeMappingContainer, configurations);
+        Map<ModuleDependency, Set<Configuration>> dependencyToConfigurations = createDependencyToConfigurationsMap(configurations);
+        Map<ModuleDependency, String> dependenciesMap = createDependencyToScopeMap(conf2ScopeMappingContainer, dependencyToConfigurations);
         List<org.apache.maven.model.Dependency> mavenDependencies = new ArrayList<org.apache.maven.model.Dependency>();
         for (ModuleDependency dependency : dependenciesMap.keySet()) {
             if (dependency.getArtifacts().size() == 0) {
-                addFromDependencyDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency));
+                addFromDependencyDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency), dependencyToConfigurations.get(dependency));
             } else {
-                addFromArtifactDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency));
+                addFromArtifactDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency), dependencyToConfigurations.get(dependency));
             }
         }
         return mavenDependencies;
     }
-
-    private Map<ModuleDependency, String> createDependencyToScopeMap(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations) {
-        Map<ModuleDependency, Set<Configuration>> dependencyToConfigurations = createDependencyToConfigurationsMap(configurations);
+    
+    private Map<ModuleDependency, String> createDependencyToScopeMap(Conf2ScopeMappingContainer conf2ScopeMappingContainer, 
+            Map<ModuleDependency, Set<Configuration>> dependencyToConfigurations) {
         Map<ModuleDependency, String> dependencyToScope = new HashMap<ModuleDependency, String>();
         for (ModuleDependency dependency : dependencyToConfigurations.keySet()) {
             Conf2ScopeMapping conf2ScopeDependencyMapping = conf2ScopeMappingContainer.getMapping(dependencyToConfigurations.get(dependency));
@@ -90,25 +91,29 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
         return dependencySetMap;
     }
 
-    private void addFromArtifactDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope) {
+    private void addFromArtifactDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope, 
+            Set<Configuration> configurations) {
         for (DependencyArtifact artifact : dependency.getArtifacts()) {
-            mavenDependencies.add(createMavenDependencyFromArtifactDescriptor(dependency, artifact, scope));
+            mavenDependencies.add(createMavenDependencyFromArtifactDescriptor(dependency, artifact, scope, configurations));
         }
     }
 
-    private void addFromDependencyDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope) {
-        mavenDependencies.add(createMavenDependencyFromDependencyDescriptor(dependency, scope));
+    private void addFromDependencyDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope, 
+            Set<Configuration> configurations) {
+        mavenDependencies.add(createMavenDependencyFromDependencyDescriptor(dependency, scope, configurations));
     }
 
-    private Dependency createMavenDependencyFromArtifactDescriptor(ModuleDependency dependency, DependencyArtifact artifact, String scope) {
-        return createMavenDependency(dependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier());
+    private Dependency createMavenDependencyFromArtifactDescriptor(ModuleDependency dependency, DependencyArtifact artifact, String scope,
+            Set<Configuration> configurations) {
+        return createMavenDependency(dependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier(), configurations);
     }
 
-    private Dependency createMavenDependencyFromDependencyDescriptor(ModuleDependency dependency, String scope) {
-        return createMavenDependency(dependency, dependency.getName(), null, scope, null);
+    private Dependency createMavenDependencyFromDependencyDescriptor(ModuleDependency dependency, String scope, Set<Configuration> configurations) {
+        return createMavenDependency(dependency, dependency.getName(), null, scope, null, configurations);
     }
 
-    private Dependency createMavenDependency(ModuleDependency dependency, String name, String type, String scope, String classifier) {
+    private Dependency createMavenDependency(ModuleDependency dependency, String name, String type, String scope, String classifier,
+            Set<Configuration> configurations) {
         Dependency mavenDependency =  new Dependency();
         mavenDependency.setGroupId(dependency.getGroup());
         mavenDependency.setArtifactId(name);
@@ -117,13 +122,17 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
         mavenDependency.setScope(scope);
         mavenDependency.setOptional(false);
         mavenDependency.setClassifier(classifier);
-        mavenDependency.setExclusions(getExclusions(dependency));
+        mavenDependency.setExclusions(getExclusions(dependency, configurations));
         return mavenDependency;
     }
 
-    private List<Exclusion> getExclusions(ModuleDependency dependency) {
+    private List<Exclusion> getExclusions(ModuleDependency dependency, Set<Configuration> configurations) {
         List<Exclusion> mavenExclusions = new ArrayList<Exclusion>();
-        for (ExcludeRule excludeRule : dependency.getExcludeRules()) {
+        Set<ExcludeRule> excludeRules = new HashSet<ExcludeRule>(dependency.getExcludeRules());
+        for (Configuration configuration : configurations) {
+            excludeRules.addAll(configuration.getExcludeRules());
+        }
+        for (ExcludeRule excludeRule : excludeRules) {
             Exclusion mavenExclusion = excludeRuleConverter.convert(excludeRule);
             if (mavenExclusion != null) {
                 mavenExclusions.add(mavenExclusion);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
index 2aea790..b886907 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
@@ -39,12 +39,12 @@ import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
 import org.apache.maven.artifact.ant.Pom;
 import org.apache.maven.settings.Settings;
 import org.apache.tools.ant.Project;
+import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.*;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.listener.ActionBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.AntUtil;
 
@@ -70,6 +70,8 @@ public abstract class AbstractMavenResolver implements MavenResolver {
 
     private LoggingManagerInternal loggingManager;
 
+    private final ActionBroadcast<MavenDeployment> beforeDeploymentActions = new ActionBroadcast<MavenDeployment>();
+
     public AbstractMavenResolver(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
         this.name = name;
         this.pomFilterContainer = pomFilterContainer;
@@ -169,11 +171,12 @@ public abstract class AbstractMavenResolver implements MavenResolver {
 
     public void commitPublishTransaction() throws IOException {
         InstallDeployTaskSupport installDeployTaskSupport = createPreConfiguredTask(AntUtil.createProject());
-        Set<DeployableFilesInfo> deployableFilesInfos = getArtifactPomContainer().createDeployableFilesInfos();
+        Set<DefaultMavenDeployment> defaultMavenDeployments = getArtifactPomContainer().createDeployableFilesInfos();
         File emptySettingsXml = createEmptyMavenSettingsXml();
         installDeployTaskSupport.setSettingsFile(emptySettingsXml);
-        for (DeployableFilesInfo deployableFilesInfo : deployableFilesInfos) {
-            addPomAndArtifact(installDeployTaskSupport, deployableFilesInfo);
+        for (DefaultMavenDeployment defaultMavenDeployment : defaultMavenDeployments) {
+            beforeDeploymentActions.execute(defaultMavenDeployment);
+            addPomAndArtifact(installDeployTaskSupport, defaultMavenDeployment);
             execute(installDeployTaskSupport);
         }
         emptySettingsXml.delete();
@@ -189,13 +192,15 @@ public abstract class AbstractMavenResolver implements MavenResolver {
         }
     }
 
-    private void addPomAndArtifact(InstallDeployTaskSupport installOrDeployTask, DeployableFilesInfo deployableFilesInfo) {
+    private void addPomAndArtifact(InstallDeployTaskSupport installOrDeployTask, DefaultMavenDeployment defaultMavenDeployment) {
         Pom pom = new Pom();
         pom.setProject(installOrDeployTask.getProject());
-        pom.setFile(deployableFilesInfo.getPomFile());
+        pom.setFile(defaultMavenDeployment.getPomArtifact().getFile());
         installOrDeployTask.addPom(pom);
-        installOrDeployTask.setFile(deployableFilesInfo.getArtifactFile());
-        for (ClassifierArtifact classifierArtifact : deployableFilesInfo.getClassifierArtifacts()) {
+        if (defaultMavenDeployment.getMainArtifact() != null) {
+            installOrDeployTask.setFile(defaultMavenDeployment.getMainArtifact().getFile());
+        }
+        for (PublishArtifact classifierArtifact : defaultMavenDeployment.getAttachedArtifacts()) {
             AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
             attachedArtifact.setClassifier(classifierArtifact.getClassifier());
             attachedArtifact.setFile(classifierArtifact.getFile());
@@ -288,4 +293,12 @@ public abstract class AbstractMavenResolver implements MavenResolver {
     public void setPomFilterContainer(PomFilterContainer pomFilterContainer) {
         this.pomFilterContainer = pomFilterContainer;
     }
+
+    public void beforeDeployment(Action<? super MavenDeployment> action) {
+        beforeDeploymentActions.add(action);
+    }
+
+    public void beforeDeployment(Closure action) {
+        beforeDeploymentActions.add(action);
+    }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java
index 48abb5e..73a882b 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.artifacts.publish.maven.deploy;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.MavenPom;
 
 import java.io.File;
@@ -25,15 +26,16 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public interface ArtifactPom {
-    Artifact getArtifact();
-
-    File getArtifactFile();
+    /**
+     * @return The main artifact, may be null.
+     */
+    PublishArtifact getArtifact();
 
     MavenPom getPom();
 
     void addArtifact(Artifact artifact, File src);
 
-    Set<ClassifierArtifact> getClassifiers();
+    Set<PublishArtifact> getAttachedArtifacts();
 
-    void writePom(File pomFile);
+    PublishArtifact writePom(File pomFile);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java
index cea01cc..0feb1ae 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java
@@ -26,5 +26,5 @@ import java.util.Set;
 public interface ArtifactPomContainer {
     void addArtifact(Artifact artifact, File src);
 
-    Set<DeployableFilesInfo> createDeployableFilesInfos();
+    Set<DefaultMavenDeployment> createDeployableFilesInfos();
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java
index cdbee69..4bdd9ca 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java
@@ -15,31 +15,33 @@
  */
 package org.gradle.api.internal.artifacts.publish.maven.deploy;
 
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.ObjectUtils;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.maven.project.MavenProject;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.artifacts.publish.AbstractPublishArtifact;
 
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultArtifactPom implements ArtifactPom {
+    private static final Set<String> PACKAGING_TYPES = Sets.newHashSet("war", "jar", "ear");
     private final MavenPom pom;
+    private final Map<ArtifactKey, PublishArtifact> artifacts = new HashMap<ArtifactKey, PublishArtifact>();
 
-    private Artifact artifact;
+    private PublishArtifact artifact;
 
-    private File artifactFile;
-
-    private final Set<ClassifierArtifact> classifiers = new HashSet<ClassifierArtifact>();
+    private final Set<PublishArtifact> classifiers = new HashSet<PublishArtifact>();
 
     public DefaultArtifactPom(MavenPom pom) {
         this.pom = pom;
@@ -49,19 +51,15 @@ public class DefaultArtifactPom implements ArtifactPom {
         return pom;
     }
 
-    public File getArtifactFile() {
-        return artifactFile;
-    }
-
-    public Artifact getArtifact() {
+    public PublishArtifact getArtifact() {
         return artifact;
     }
 
-    public Set<ClassifierArtifact> getClassifiers() {
+    public Set<PublishArtifact> getAttachedArtifacts() {
         return Collections.unmodifiableSet(classifiers);
     }
 
-    public void writePom(File pomFile) {
+    public PublishArtifact writePom(final File pomFile) {
         try {
             pomFile.getParentFile().mkdirs();
             FileWriter writer = new FileWriter(pomFile);
@@ -73,37 +71,46 @@ public class DefaultArtifactPom implements ArtifactPom {
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
+
+        return new PomArtifact(pomFile);
     }
 
     public void addArtifact(Artifact artifact, File src) {
         throwExceptionIfArtifactOrSrcIsNull(artifact, src);
-        if (hasClassifier(artifact)) {
-            addClassifierArtifact(artifact, src);
+        PublishArtifact publishArtifact = new MavenArtifact(artifact, src);
+        ArtifactKey artifactKey = new ArtifactKey(publishArtifact);
+        if (this.artifacts.containsKey(artifactKey)) {
+            throw new InvalidUserDataException(String.format("A POM cannot have multiple artifacts with the same type and classifier. Already have %s, trying to add %s.",
+                    this.artifacts.get(artifactKey), publishArtifact));
+        }
+
+        if (publishArtifact.getClassifier() != null) {
+            addArtifact(publishArtifact);
             assignArtifactValuesToPom(artifact, pom, false);
             return;
         }
+
         if (this.artifact != null) {
-            throw new InvalidUserDataException("A pom can't have multiple main artifacts. " +
-                    "Already assigned artifact: " + this.artifact + " Artifact trying to assign: " + artifact);
+            // Choose the 'main' artifact based on its type.
+            if (!PACKAGING_TYPES.contains(artifact.getType())) {
+                addArtifact(publishArtifact);
+                return;
+            }
+            if (PACKAGING_TYPES.contains(this.artifact.getType())) {
+                throw new InvalidUserDataException("A POM can not have multiple main artifacts. " +
+                        "Already have " + this.artifact + ", trying to add " + publishArtifact);
+            }
+            addArtifact(this.artifact);
         }
-        this.artifact = artifact;
-        this.artifactFile = src;
-        assignArtifactValuesToPom(artifact, pom, true);
-    }
 
-    private void addClassifierArtifact(Artifact artifact, File artifactFile) {
-        String classifier = getClassifier(artifact);
-        ClassifierArtifact classifierArtifact = new ClassifierArtifact(classifier,
-                artifact.getType(), artifactFile);
-        if (classifiers.contains(classifierArtifact)) {
-            throw new InvalidUserDataException("A pom can't have multiple artifacts for the same classifier=" + classifier +
-                    " Artifact trying to assign: " + artifact);
-        }
-        classifiers.add(classifierArtifact);
+        this.artifact = publishArtifact;
+        this.artifacts.put(artifactKey, publishArtifact);
+        assignArtifactValuesToPom(artifact, pom, true);
     }
 
-    private boolean hasClassifier(Artifact artifact) {
-        return getClassifier(artifact) != null;
+    private void addArtifact(PublishArtifact artifact) {
+        classifiers.add(artifact);
+        artifacts.put(new ArtifactKey(artifact), artifact);
     }
 
     private String getClassifier(Artifact artifact) {
@@ -133,4 +140,88 @@ public class DefaultArtifactPom implements ArtifactPom {
             throw new InvalidUserDataException("Src file must not be null.");
         }
     }
+
+    private static class ArtifactKey {
+        private final String type;
+        private final String classifier;
+
+        private ArtifactKey(PublishArtifact artifact) {
+            this.type = artifact.getType();
+            this.classifier = artifact.getClassifier();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            ArtifactKey other = (ArtifactKey) o;
+            return ObjectUtils.equals(type, other.type) && ObjectUtils.equals(classifier, other.classifier);
+        }
+
+        @Override
+        public int hashCode() {
+            return ObjectUtils.hashCode(type) ^ ObjectUtils.hashCode(classifier);
+        }
+    }
+
+    private abstract class AbstractMavenArtifact extends AbstractPublishArtifact {
+        private final File file;
+
+        protected AbstractMavenArtifact(File file) {
+            this.file = file;
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        public String getName() {
+            return pom.getArtifactId();
+        }
+
+        public Date getDate() {
+            return null;
+        }
+    }
+
+    private class MavenArtifact extends AbstractMavenArtifact {
+        private final Artifact artifact;
+
+        private MavenArtifact(Artifact artifact, File file) {
+            super(file);
+            this.artifact = artifact;
+        }
+
+        public String getClassifier() {
+            return DefaultArtifactPom.this.getClassifier(artifact);
+        }
+
+        public String getExtension() {
+            return artifact.getExt();
+        }
+
+        public String getType() {
+            return artifact.getType();
+        }
+    }
+
+    private class PomArtifact extends AbstractMavenArtifact {
+        public PomArtifact(File pomFile) {
+            super(pomFile);
+        }
+
+        public String getExtension() {
+            return "pom";
+        }
+
+        public String getType() {
+            return "pom";
+        }
+
+        public String getClassifier() {
+            return null;
+        }
+
+        public Date getDate() {
+            return null;
+        }
+    }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
index 35e9dee..45767d5 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.artifacts.publish.maven.deploy;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.PomFilterContainer;
 import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
 
@@ -56,23 +57,18 @@ public class DefaultArtifactPomContainer implements ArtifactPomContainer {
         }
     }
 
-    public Set<DeployableFilesInfo> createDeployableFilesInfos() {
-        Set<DeployableFilesInfo> deployableFilesInfos = new HashSet<DeployableFilesInfo>();
+    public Set<DefaultMavenDeployment> createDeployableFilesInfos() {
+        Set<DefaultMavenDeployment> defaultMavenDeployments = new HashSet<DefaultMavenDeployment>();
         for (String activeArtifactPomName : artifactPoms.keySet()) {
             ArtifactPom activeArtifactPom = artifactPoms.get(activeArtifactPomName);
             File pomFile = createPomFile(activeArtifactPomName);
-            activeArtifactPom.writePom(pomFile);
-            deployableFilesInfos.add(new DeployableFilesInfo(pomFile, activeArtifactPom.getArtifactFile(), activeArtifactPom.getClassifiers()));
+            PublishArtifact pomArtifact = activeArtifactPom.writePom(pomFile);
+            defaultMavenDeployments.add(new DefaultMavenDeployment(pomArtifact, activeArtifactPom.getArtifact(), activeArtifactPom.getAttachedArtifacts()));
         }
-        return deployableFilesInfos;
+        return defaultMavenDeployments;
     }
 
     private File createPomFile(String artifactPomName) {
         return new File(pomMetaInfoProvider.getMavenPomDir(), "pom-" + artifactPomName + ".xml");
     }
-
-    public Map<String, ArtifactPom> getArtifactPoms() {
-        return artifactPoms;
-    }
-
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultMavenDeployment.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultMavenDeployment.java
new file mode 100644
index 0000000..805f526
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultMavenDeployment.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.publish.maven.deploy;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.MavenDeployment;
+import org.gradle.util.GUtil;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+*/
+public class DefaultMavenDeployment implements MavenDeployment {
+    private Set<PublishArtifact> attachedArtifacts;
+    private final PublishArtifact pomArtifact;
+    private final PublishArtifact mainArtifact;
+
+    public DefaultMavenDeployment(PublishArtifact pomArtifact, PublishArtifact mainArtifact, Iterable<? extends PublishArtifact> attachedArtifacts) {
+        this.pomArtifact = pomArtifact;
+        this.mainArtifact = mainArtifact;
+        this.attachedArtifacts = GUtil.addSets(attachedArtifacts);
+    }
+
+    public void addArtifact(PublishArtifact artifact) {
+        attachedArtifacts.add(artifact);
+    }
+
+    public PublishArtifact getPomArtifact() {
+        return pomArtifact;
+    }
+
+    public Set<PublishArtifact> getArtifacts() {
+        Set<PublishArtifact> artifacts = new HashSet<PublishArtifact>();
+        artifacts.addAll(attachedArtifacts);
+        if (mainArtifact != null) {
+            artifacts.add(mainArtifact);
+        }
+        artifacts.add(pomArtifact);
+        return artifacts;
+    }
+
+    public PublishArtifact getMainArtifact() {
+        return mainArtifact;
+    }
+
+    public Set<PublishArtifact> getAttachedArtifacts() {
+        return attachedArtifacts;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployableFilesInfo.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployableFilesInfo.java
deleted file mode 100644
index 088f45b..0000000
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployableFilesInfo.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.publish.maven.deploy;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
-*/
-public class DeployableFilesInfo {
-    private File pomFile;
-    private File artifactFile;
-    private Set<ClassifierArtifact> classifierArtifacts;
-
-    public DeployableFilesInfo(File pomFile, File artifactFile, Set<ClassifierArtifact> classifierArtifacts) {
-        this.pomFile = pomFile;
-        this.artifactFile = artifactFile;
-        this.classifierArtifacts = classifierArtifacts;
-    }
-
-    public File getPomFile() {
-        return pomFile;
-    }
-
-    public File getArtifactFile() {
-        return artifactFile;
-    }
-
-    public Set<ClassifierArtifact> getClassifierArtifacts() {
-        return classifierArtifacts;
-    }
-}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/IdePlugin.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/IdePlugin.java
new file mode 100644
index 0000000..593d069
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/IdePlugin.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.plugins;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Delete;
+
+public abstract class IdePlugin implements Plugin<Project> {
+    private Task lifecycleTask;
+    private Task cleanTask;
+    private Project project;
+
+    public void apply(Project target) {
+        project = target;
+        String lifecyleTaskName = getLifecycleTaskName();
+        lifecycleTask = target.task(lifecyleTaskName);
+        lifecycleTask.setGroup("IDE");
+        cleanTask = target.task(cleanName(lifecyleTaskName));
+        cleanTask.setGroup("IDE");
+        onApply(target);
+    }
+
+    public Task getLifecycleTask() {
+        return lifecycleTask;
+    }
+
+    public Task getCleanTask() {
+        return cleanTask;
+    }
+
+    public Task getCleanTask(Task worker) {
+        return project.getTasks().getByName(cleanName(worker.getName()));
+    }
+
+    private String cleanName(String taskName) {
+        return String.format("clean%s", StringUtils.capitalize(taskName));
+    }
+
+    protected void addWorker(Task worker) {
+        lifecycleTask.dependsOn(worker);
+        Delete cleanWorker = project.getTasks().add(cleanName(worker.getName()), Delete.class);
+        cleanWorker.delete(worker.getOutputs().getFiles());
+        cleanTask.dependsOn(cleanWorker);
+    }
+    
+    protected void onApply(Project target) {
+    }
+
+    protected abstract String getLifecycleTaskName();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
index 7e0d061..3786dbf 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.project.ant;
 
 import groovy.util.AntBuilder;
+import org.apache.tools.ant.Project;
 import org.apache.tools.ant.Target;
 import org.gradle.api.internal.file.ant.AntFileResource;
 import org.gradle.api.internal.file.ant.BaseDirSelector;
@@ -62,6 +63,17 @@ public class BasicAntBuilder extends org.gradle.api.AntBuilder {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    protected void nodeCompleted(Object parent, Object node) {
+        ClassLoader original = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(Project.class.getClassLoader());
+        try {
+            super.nodeCompleted(parent, node);
+        } finally {
+            Thread.currentThread().setContextClassLoader(original);
+        }
+    }
+
     protected Object postNodeCompletion(Object parent, Object node) {
         try {
             return nodeField.get(this);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
index 0fb1086..1b37fab 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
@@ -46,7 +46,7 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         Task task = taskFactory.createTask(project, mutableOptions);
         String name = task.getName();
 
-        if (!replace && findByName(name) != null) {
+        if (!replace && findByNameWithoutRules(name) != null) {
             throw new InvalidUserDataException(String.format(
                     "Cannot add %s as a task with that name already exists.", task));
         }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/AbstractPersistableConfigurationObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/AbstractPersistableConfigurationObject.java
new file mode 100644
index 0000000..5858b10
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/AbstractPersistableConfigurationObject.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator;
+
+import org.gradle.util.UncheckedException;
+
+import java.io.*;
+
+public abstract class AbstractPersistableConfigurationObject implements PersistableConfigurationObject {
+    public void load(File inputFile) {
+        try {
+            InputStream inputStream = new FileInputStream(inputFile);
+            try {
+                load(inputStream);
+            } finally {
+                inputStream.close();
+            }
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+    }
+
+    public void loadDefaults() {
+        try {
+            InputStream inputStream = getClass().getResourceAsStream(getDefaultResourceName());
+            try {
+                load(inputStream);
+            } finally {
+                inputStream.close();
+            }
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+    }
+
+    public abstract void load(InputStream inputStream) throws Exception;
+
+    public void store(File outputFile) {
+        try {
+            OutputStream outputStream = new FileOutputStream(outputFile);
+            try {
+                store(outputStream);
+            } finally {
+                outputStream.close();
+            }
+        } catch (IOException e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+    }
+
+    public abstract void store(OutputStream outputStream);
+
+    protected abstract String getDefaultResourceName();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/Generator.java
similarity index 64%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/Generator.java
index 0647de2..c748703 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/Generator.java
@@ -1,25 +1,32 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import java.net.URI;
-
-public interface OutgoingConnector {
-    /**
-     * Creates a connection to the given address.
-     */
-    Connection<Message> connect(URI destinationUri);
-}
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator;
+
+import java.io.File;
+
+/**
+ * Responsible for reading, configuring and writing a config object of type T to/from a file.
+ * @param <T>
+ */
+public interface Generator<T> {
+    T read(File inputFile);
+
+    T defaultInstance();
+
+    void configure(T object);
+
+    void write(T object, File outputFile);
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObject.java
similarity index 74%
copy from subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObject.java
index d8bb8b6..e87524b 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObject.java
@@ -13,8 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.launcher;
+package org.gradle.api.internal.tasks.generator;
 
-public interface BuildCompleter {
-    void exit(Throwable failure);
+import java.io.File;
+
+public interface PersistableConfigurationObject {
+    void load(File inputFile);
+
+    void loadDefaults();
+
+    void store(File outputFile);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObjectGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObjectGenerator.java
new file mode 100644
index 0000000..7d5efab
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObjectGenerator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator;
+
+import org.gradle.api.internal.Factory;
+
+import java.io.File;
+
+/**
+ * Adapts a {@link PersistableConfigurationObject} to a {@link
+ * Generator}.
+ *
+ * @param <T> the configuration object type.
+ */
+public abstract class PersistableConfigurationObjectGenerator<T extends PersistableConfigurationObject> implements Generator<T>, Factory<T> {
+    public T read(File inputFile) {
+        T obj = create();
+        obj.load(inputFile);
+        return obj;
+    }
+
+    public T defaultInstance() {
+        T obj = create();
+        obj.loadDefaults();
+        return obj;
+    }
+
+    public void write(T object, File outputFile) {
+        object.store(outputFile);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PropertiesPersistableConfigurationObject.java
similarity index 52%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PropertiesPersistableConfigurationObject.java
index 7634f3f..7d66ac3 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/PropertiesPersistableConfigurationObject.java
@@ -13,23 +13,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.configuration;
+package org.gradle.api.internal.tasks.generator;
 
 import org.gradle.util.UncheckedException;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
 
-public class GradleLauncherMetaData {
-    public void describeCommand(Appendable output, String... args) {
+public abstract class PropertiesPersistableConfigurationObject extends AbstractPersistableConfigurationObject {
+    private Properties properties;
+
+    @Override
+    public void load(InputStream inputStream) throws Exception {
+        properties = new Properties();
+        properties.load(inputStream);
+        load(properties);
+    }
+
+    @Override
+    public void store(OutputStream outputStream) {
+        store(properties);
         try {
-            String appName = System.getProperty("org.gradle.appname", "gradle");
-            output.append(appName);
-            for (String arg : args) {
-                output.append(' ');
-                output.append(arg);
-            }
+            properties.store(outputStream, "");
         } catch (IOException e) {
             throw UncheckedException.asUncheckedException(e);
         }
     }
+
+    protected abstract void store(Properties properties);
+
+    protected abstract void load(Properties properties);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/XmlPersistableConfigurationObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/XmlPersistableConfigurationObject.java
new file mode 100644
index 0000000..a0db86d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/generator/XmlPersistableConfigurationObject.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator;
+
+import groovy.util.Node;
+import groovy.util.XmlParser;
+import org.gradle.api.internal.XmlTransformer;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+/**
+ * A {@link PersistableConfigurationObject} which is stored in an XML file.
+ */
+public abstract class XmlPersistableConfigurationObject extends AbstractPersistableConfigurationObject {
+    private final XmlTransformer xmlTransformer;
+    private Node xml;
+
+    protected XmlPersistableConfigurationObject(XmlTransformer xmlTransformer) {
+        this.xmlTransformer = xmlTransformer;
+    }
+
+    public Node getXml() {
+        return xml;
+    }
+
+    @Override
+    public void load(InputStream inputStream) throws Exception {
+        xml = new XmlParser().parse(inputStream);
+        load(xml);
+    }
+
+    @Override
+    public void store(OutputStream outputStream) {
+        store(xml);
+        xmlTransformer.transform(xml, new OutputStreamWriter(outputStream));
+    }
+
+    /**
+     * Called immediately after the XML file has been read.
+     */
+    protected abstract void load(Node xml);
+
+    /**
+     * Called immediately before the XML file is to be written.
+     */
+    protected abstract void store(Node xml);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java
index e3991d3..b60d90d 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java
@@ -109,13 +109,55 @@ public interface Gradle {
     /**
      * Adds a closure to be called immediately after a project is evaluated. The project is passed to the closure as the
      * first parameter. The project evaluation failure, if any, is passed as the second parameter. Both parameters are
-     * options.
+     * optional.
      *
      * @param closure The closure to execute.
      */
     void afterProject(Closure closure);
 
     /**
+     * Adds a closure to be called when the build is started. This {@code Gradle} instance is passed to the closure as
+     * the first parameter.
+     *
+     * @param closure The closure to execute.
+     */
+    void buildStarted(Closure closure);
+
+    /**
+     * Adds a closure to be called when the build settings have been loaded and evaluated. The settings object is
+     * fully configured and is ready to use to load the build projects. The
+     * {@link org.gradle.api.initialization.Settings} object is passed to the closure as a parameter.
+     *
+     * @param closure The closure to execute.
+     */
+    void settingsEvaluated(Closure closure);
+
+    /**
+     * Adds a closure to be called when the projects for the build have been created from the settings.
+     * None of the projects have been evaluated. This {@code Gradle} instance is passed to the closure as a parameter.
+     *
+     * @param closure The closure to execute.
+     */
+    void projectsLoaded(Closure closure);
+
+    /**
+     * Adds a closure to be called when all projects for the build have been evaluated. The project objects are fully
+     * configured and are ready to use to populate the task graph. This {@code Gradle} instance is passed to
+     * the closure as a parameter.
+     *
+     * @param closure The closure to execute.
+     */
+    void projectsEvaluated(Closure closure);
+
+    /**
+     * Adds a closure to be called when the build is completed. All selected tasks have been executed.
+     * A {@link org.gradle.BuildResult} instance is passed to the closure as a parameter.
+     *
+     * @param closure The closure to execute.
+     */
+    void buildFinished(Closure closure);
+
+    /**
      * <p>Adds a {@link BuildListener} to this Build instance. The listener is notified of events which occur during the
      * execution of the build.</p>
      *
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/GeneratorTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/GeneratorTask.java
new file mode 100644
index 0000000..446322d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/GeneratorTask.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.tasks.generator.Generator;
+import org.gradle.api.specs.Specs;
+import org.gradle.listener.ActionBroadcast;
+
+import java.io.File;
+
+/**
+ * <p>A {@code GeneratorTask} generates a configuration file based on a domain object of type T. When executed, the
+ * task:</p>
+ *
+ * <ul>
+ *
+ * <li>loads the object from the input file, if it exists.</li>
+ *
+ * <li>Calls the beforeConfigured actions, passing the object to each action.</li>
+ *
+ * <li>Configures the object in some task-specific way.</li>
+ *
+ * <li>Calls the afterConfigured actions, passing the object to each action.</li>
+ *
+ * <li>writes the object to the output file.</li>
+ *
+ * </ul>
+ *
+ * @param <T> The domain object for the configuration file.
+ */
+public class GeneratorTask<T> extends ConventionTask {
+    private File inputFile;
+    private File outputFile;
+    private final ActionBroadcast<T> beforeConfigured = new ActionBroadcast<T>();
+    private final ActionBroadcast<T> afterConfigured = new ActionBroadcast<T>();
+    protected Generator<T> generator;
+
+    public GeneratorTask() {
+        getOutputs().upToDateWhen(Specs.satisfyNone());
+    }
+
+    @TaskAction
+    void generate() {
+        File inputFile = getInputFile();
+        T object;
+        if (inputFile.exists()) {
+            object = generator.read(inputFile);
+        } else {
+            object = generator.defaultInstance();
+        }
+        beforeConfigured.execute(object);
+        generator.configure(object);
+        afterConfigured.execute(object);
+        generator.write(object, getOutputFile());
+    }
+
+    /**
+     * The input file to load the initial configuration from. Defaults to the output file. If the specified input file
+     * does not exist, this task uses a default initial configuration.
+     *
+     * @return The input file.
+     */
+    public File getInputFile() {
+        return inputFile != null ? inputFile : getOutputFile();
+    }
+
+    /**
+     * Sets the input file to load the initial configuration from.
+     *
+     * @param inputFile The input file. Use null to use the output file.
+     */
+    public void setInputFile(File inputFile) {
+        this.inputFile = inputFile;
+    }
+
+    /**
+     * The output file to write the final configuration to.
+     *
+     * @return The output file.
+     */
+    @OutputFile
+    public File getOutputFile() {
+        return outputFile;
+    }
+
+    /**
+     * Sets the output file to write the final configuration to.
+     *
+     * @param outputFile The output file.
+     */
+    public void setOutputFile(File outputFile) {
+        this.outputFile = outputFile;
+    }
+
+    /**
+     * <p>Adds a closure to be called before the domain object is configured by this task. The domain object is passed
+     * as a parameter to the closure.</p>
+     *
+     * <p>The closure is executed after the domain object has been loaded from the input file. Using this method allows
+     * you to change the domain object in some way before the task configures it.</p>
+     *
+     * @param closure The closure to execute.
+     */
+    public void beforeConfigured(Closure closure) {
+        beforeConfigured.add(closure);
+    }
+
+    /**
+     * <p>Adds an action to be called before the domain object is configured by this task. The domain object is passed
+     * as a parameter to the action.</p>
+     *
+     * <p>The action is executed after the domain object has been loaded from the input file. Using this method allows
+     * you to change the domain object in some way before the task configures it.</p>
+     *
+     * @param action The action to execute.
+     */
+    public void beforeConfigured(Action<? super T> action) {
+        beforeConfigured.add(action);
+    }
+
+    /**
+     * <p>Adds a closure to be called after the domain object has been configured by this task. The domain object is
+     * passed as a parameter to the closure.</p>
+     *
+     * <p>The closure is executed just before the domain object is written to the output file. Using this method allows
+     * you to override the configuration applied by this task.</p>
+     *
+     * @param closure The closure to execute.
+     */
+    public void whenConfigured(Closure closure) {
+        afterConfigured.add(closure);
+    }
+
+    /**
+     * <p>Adds an action to be called after the domain object has been configured by this task. The domain object is
+     * passed as a parameter to the action.</p>
+     *
+     * <p>The action is executed just before the domain object is written to the output file. Using this method allows
+     * you to override the configuration applied by this task.</p>
+     *
+     * @param action The action to execute.
+     */
+    public void whenConfigured(Action<? super T> action) {
+        afterConfigured.add(action);
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/XmlGeneratorTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/XmlGeneratorTask.java
new file mode 100644
index 0000000..3f07ed4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/XmlGeneratorTask.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.maven.XmlProvider;
+import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.internal.tasks.generator.PersistableConfigurationObject;
+import org.gradle.api.internal.tasks.generator.PersistableConfigurationObjectGenerator;
+
+/**
+ * A convenience superclass for those tasks which generate XML configuration files from a domain object of type T.
+ *
+ * @param <T> The domain object type.
+ */
+public abstract class XmlGeneratorTask<T extends PersistableConfigurationObject> extends GeneratorTask<T> {
+    private final XmlTransformer xmlTransformer = new XmlTransformer();
+
+    public XmlGeneratorTask() {
+        generator = new PersistableConfigurationObjectGenerator<T>() {
+            public T create() {
+                return XmlGeneratorTask.this.create();
+            }
+
+            public void configure(T object) {
+                XmlGeneratorTask.this.configure(object);
+            }
+        };
+    }
+
+    protected XmlTransformer getXmlTransformer() {
+        return xmlTransformer;
+    }
+
+    protected abstract void configure(T object);
+
+    protected abstract T create();
+
+    /**
+     * Adds a closure to be called when the XML document has been created. The XML is passed to the closure as a
+     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The closure can modify the XML before
+     * it is written to the output file.
+     *
+     * @param closure The closure to execute when the XML has been created.
+     */
+    public void withXml(Closure closure) {
+        xmlTransformer.addAction(closure);
+    }
+
+    /**
+     * Adds an action to be called when the XML document has been created. The XML is passed to the action as a
+     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The action can modify the XML before
+     * it is written to the output file.
+     *
+     * @param action The action to execute when the IPR XML has been created.
+     */
+    public void withXml(Action<? super XmlProvider> action) {
+        xmlTransformer.addAction(action);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
index baa38f4..0952689 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTask.java
@@ -21,8 +21,8 @@ import org.gradle.api.DefaultTask;
 import org.gradle.api.Project;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.diagnostics.internal.GraphRenderer;
-import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.configuration.ImplicitTasksConfigurer;
+import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.util.GUtil;
@@ -32,7 +32,6 @@ import java.util.Collections;
 import java.util.List;
 
 import static org.gradle.logging.StyledTextOutput.Style.*;
-import static org.gradle.logging.StyledTextOutput.Style.UserInput;
 
 /**
  * <p>Displays a list of projects in the build. It is used when you use the project list command-line option.</p>
@@ -42,7 +41,7 @@ public class ProjectReportTask extends DefaultTask {
 
     @TaskAction
     void listProjects() {
-        GradleLauncherMetaData metaData = new GradleLauncherMetaData();
+        BuildClientMetaData metaData = getServices().get(BuildClientMetaData.class);
         Project project = getProject();
 
         textOutput.println();
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
index 7634f3f..1cf07fd 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
@@ -15,14 +15,17 @@
  */
 package org.gradle.configuration;
 
+import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.util.UncheckedException;
 
 import java.io.IOException;
+import java.io.Serializable;
+
+public class GradleLauncherMetaData implements Serializable, BuildClientMetaData {
+    private final String appName = System.getProperty("org.gradle.appname", "gradle");
 
-public class GradleLauncherMetaData {
     public void describeCommand(Appendable output, String... args) {
         try {
-            String appName = System.getProperty("org.gradle.appname", "gradle");
             output.append(appName);
             for (String arg : args) {
                 output.append(' ');
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/Help.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/Help.java
index f4309b2..b8e2a35 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/Help.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/Help.java
@@ -17,17 +17,18 @@ package org.gradle.configuration;
 
 import org.gradle.api.DefaultTask;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.util.GradleVersion;
 
-import static org.gradle.logging.StyledTextOutput.Style.UserInput;
+import static org.gradle.logging.StyledTextOutput.Style.*;
 
 public class Help extends DefaultTask {
     @TaskAction
     void displayHelp() {
         StyledTextOutput output = getServices().get(StyledTextOutputFactory.class).create(Help.class);
-        GradleLauncherMetaData metaData = new GradleLauncherMetaData();
+        BuildClientMetaData metaData = getServices().get(BuildClientMetaData.class);
 
         output.println();
         output.formatln("Welcome to Gradle %s.", new GradleVersion().getVersion());
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java
index 8afbef5..d1d9be3 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java
@@ -23,4 +23,16 @@ public abstract class AbstractCommandLineConverter<T> implements CommandLineConv
         configure(parser);
         return convert(parser.parse(args));
     }
+
+    public T convert(ParsedCommandLine args) throws CommandLineArgumentException {
+        return convert(args, newInstance());
+    }
+
+    public T convert(Iterable<String> args, T target) throws CommandLineArgumentException {
+        CommandLineParser parser = new CommandLineParser();
+        configure(parser);
+        return convert(parser.parse(args), target);
+    }
+
+    protected abstract T newInstance();
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildClientMetaData.java
similarity index 65%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildClientMetaData.java
index 0647de2..863ac4c 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildClientMetaData.java
@@ -1,25 +1,26 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import java.net.URI;
-
-public interface OutgoingConnector {
-    /**
-     * Creates a connection to the given address.
-     */
-    Connection<Message> connect(URI destinationUri);
-}
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization;
+
+/**
+ * A bunch of information about the client used to start this build.
+ */
+public interface BuildClientMetaData {
+    /**
+     * Appends a message to the given appendable describing how to run the client with the given command-line args.
+     */
+    void describeCommand(Appendable output, String... args);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildRequestMetaData.java
similarity index 59%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildRequestMetaData.java
index 0647de2..9412b62 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildRequestMetaData.java
@@ -1,25 +1,33 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import java.net.URI;
-
-public interface OutgoingConnector {
-    /**
-     * Creates a connection to the given address.
-     */
-    Connection<Message> connect(URI destinationUri);
-}
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization;
+
+import org.gradle.util.Clock;
+
+/**
+ * A bunch of information about the request which launched a build.
+ */
+public interface BuildRequestMetaData {
+    /**
+     * Returns the meta-data about the client used to launch this build.
+     */
+    BuildClientMetaData getClient();
+
+    /**
+     * Returns a clock measuring the time since the request was made by the user of the client.
+     */
+    Clock getBuildTimeClock();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java
index dd76c54..15ea697 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java
@@ -22,8 +22,12 @@ import org.gradle.CommandLineArgumentException;
  */
 public interface CommandLineConverter<T> {
     T convert(Iterable<String> args) throws CommandLineArgumentException;
-    
+
+    T convert(Iterable<String> args, T target) throws CommandLineArgumentException;
+
     T convert(ParsedCommandLine args) throws CommandLineArgumentException;
 
+    T convert(ParsedCommandLine args, T target) throws CommandLineArgumentException;
+
     void configure(CommandLineParser parser);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineParser.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineParser.java
index fd7173d..62be97e 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineParser.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLineParser.java
@@ -79,48 +79,48 @@ public class CommandLineParser {
      */
     public ParsedCommandLine parse(Iterable<String> commandLine) {
         ParsedCommandLine parsedCommandLine = new ParsedCommandLine(new HashSet<CommandLineOption>(optionsByString.values()));
-        ParseState parseState = new BeforeSubcommand(parsedCommandLine);
+        ParserState parseState = new BeforeFirstSubCommand(parsedCommandLine);
         for (String arg : commandLine) {
             if (parseState.maybeStartOption(arg)) {
                 if (arg.equals("--")) {
-                    parseState = new OptionsComplete(parsedCommandLine);
+                    parseState = new AfterOptions(parsedCommandLine);
                 } else if (arg.matches("--[^=]+")) {
-                    OptionParseState parsedOption = parseState.addOption(arg, arg.substring(2));
-                    parseState = parsedOption.asNextArg();
+                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2));
+                    parseState = parsedOption.onStartNextArg();
                 } else if (arg.matches("--[^=]+=.*")) {
                     int endArg = arg.indexOf('=');
-                    OptionParseState parsedOption = parseState.addOption(arg, arg.substring(2, endArg));
-                    parsedOption.addArgument(arg.substring(endArg + 1));
+                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2, endArg));
+                    parseState = parsedOption.onArgument(arg.substring(endArg + 1));
                 } else if (arg.matches("-[^=]=.*")) {
-                    OptionParseState parsedOption = parseState.addOption(arg, arg.substring(1, 2));
-                    parsedOption.addArgument(arg.substring(3));
+                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(1, 2));
+                    parseState = parsedOption.onArgument(arg.substring(3));
                 } else {
                     assert arg.matches("-[^-].*");
                     String option = arg.substring(1);
                     if (optionsByString.containsKey(option)) {
-                        OptionParseState parsedOption = parseState.addOption(arg, option);
-                        parseState = parsedOption.asNextArg();
+                        OptionParserState parsedOption = parseState.onStartOption(arg, option);
+                        parseState = parsedOption.onStartNextArg();
                     } else {
                         String option1 = arg.substring(1, 2);
-                        OptionParseState parsedOption = parseState.addOption("-" + option1, option1);
+                        OptionParserState parsedOption = parseState.onStartOption("-" + option1, option1);
                         if (parsedOption.getHasArgument()) {
-                            parsedOption.addArgument(arg.substring(2));
+                            parseState = parsedOption.onArgument(arg.substring(2));
                         } else {
-                            parseState = parsedOption.argumentMissing();
+                            parseState = parsedOption.onComplete();
                             for (int i = 2; i < arg.length(); i++) {
                                 String optionStr = arg.substring(i, i + 1);
-                                parsedOption = parseState.addOption("-" + optionStr, optionStr);
-                                parseState = parsedOption.argumentMissing();
+                                parsedOption = parseState.onStartOption("-" + optionStr, optionStr);
+                                parseState = parsedOption.onComplete();
                             }
                         }
                     }
                 }
             } else {
-                parseState = parseState.onExtraValue(arg);
+                parseState = parseState.onNonOption(arg);
             }
         }
 
-        parseState.complete();
+        parseState.onCommandLineEnd();
         return parsedCommandLine;
     }
 
@@ -207,162 +207,223 @@ public class CommandLineParser {
         }
     }
 
-    private static abstract class ParseState {
-        public boolean maybeStartOption(String arg) {
+    private static abstract class ParserState {
+        public abstract boolean maybeStartOption(String arg);
+
+        boolean isOption(String arg) {
             return arg.matches("-.+");
         }
 
-        public abstract ParseState onExtraValue(String arg);
+        public abstract OptionParserState onStartOption(String arg, String option);
 
-        public void complete() {
-        }
+        public abstract ParserState onNonOption(String arg);
 
-        public OptionParseState addOption(String arg, String option) {
-            return addOption(new OptionString(arg, option));
+        public void onCommandLineEnd() {
         }
-
-        public abstract OptionParseState addOption(OptionString option);
     }
 
-    private static class OptionParseState extends ParseState {
-        private final ParsedCommandLineOption option;
-        private final OptionString actualOption;
-        private final ParseState nextState;
+    private abstract class OptionAwareParserState extends ParserState {
+        protected final ParsedCommandLine commandLine;
 
-        private OptionParseState(ParsedCommandLineOption option, OptionString actualOption, ParseState nextState) {
-            this.option = option;
-            this.actualOption = actualOption;
-            this.nextState = nextState;
+        protected OptionAwareParserState(ParsedCommandLine commandLine) {
+            this.commandLine = commandLine;
         }
 
         @Override
         public boolean maybeStartOption(String arg) {
-            if (super.maybeStartOption(arg)) {
-                argumentMissing();
-                return true;
-            } else {
-                return false;
-            }
+            return isOption(arg);
         }
 
         @Override
-        public ParseState onExtraValue(String arg) {
-            addArgument(arg);
-            return nextState;
+        public ParserState onNonOption(String arg) {
+            commandLine.addExtraValue(arg);
+            return allowMixedOptions ? new AfterFirstSubCommand(commandLine) : new AfterOptions(commandLine);
         }
+    }
 
-        @Override
-        public void complete() {
-            argumentMissing();
+    private class BeforeFirstSubCommand extends OptionAwareParserState {
+        private BeforeFirstSubCommand(ParsedCommandLine commandLine) {
+            super(commandLine);
         }
 
         @Override
-        public OptionParseState addOption(OptionString option) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void addArgument(String argument) {
-            if (!getHasArgument()) {
-                throw new CommandLineArgumentException(String.format("Command-line option '%s' does not take an argument.", actualOption));
-            }
-            if (argument.length() == 0) {
-                throw new CommandLineArgumentException(String.format("An empty argument was provided for command-line option '%s'.", actualOption));
+        public OptionParserState onStartOption(String arg, String option) {
+            OptionString optionString = new OptionString(arg, option);
+            CommandLineOption commandLineOption = optionsByString.get(option);
+            if (commandLineOption == null) {
+                throw new CommandLineArgumentException(String.format("Unknown command-line option '%s'.", optionString));
             }
-            if (!option.getValues().isEmpty() && !option.getOption().getAllowsMultipleArguments()) {
-                throw new CommandLineArgumentException(String.format("Multiple arguments were provided for command-line option '%s'.", actualOption));
-            }
-            option.addArgument(argument);
-        }
-
-        public boolean getHasArgument() {
-            return option.getOption().getAllowsArguments();
+            return new KnownOptionParserState(optionString, commandLineOption, commandLine, this);
         }
+    }
 
-        public ParseState asNextArg() {
-            return getHasArgument() ? this : nextState;
+    private class AfterFirstSubCommand extends OptionAwareParserState {
+        private AfterFirstSubCommand(ParsedCommandLine commandLine) {
+            super(commandLine);
         }
 
-        public ParseState argumentMissing() {
-            if (!getHasArgument()) {
-                return nextState;
+        @Override
+        public OptionParserState onStartOption(String arg, String option) {
+            CommandLineOption commandLineOption = optionsByString.get(option);
+            if (commandLineOption == null) {
+                return new UnknownOptionParserState(arg, commandLine, this);
             }
-            throw new CommandLineArgumentException(String.format("No argument was provided for command-line option '%s'.", actualOption));
+            return new KnownOptionParserState(new OptionString(arg, option), commandLineOption, commandLine, this);
         }
     }
 
-    private static class GlobalParseState extends ParseState {
-        final ParsedCommandLine commandLine;
+    private static class AfterOptions extends ParserState {
+        private final ParsedCommandLine commandLine;
 
-        GlobalParseState(ParsedCommandLine commandLine) {
+        private AfterOptions(ParsedCommandLine commandLine) {
             this.commandLine = commandLine;
         }
 
         @Override
-        public OptionParseState addOption(OptionString option) {
-            ParsedCommandLineOption parsedOption = commandLine.addOption(option.option);
-            if (parsedOption == null) {
-                return onUnknownOption(option);
-            }
-            if (parsedOption.getOption().getSubcommand() != null) {
-                ParseState nextState = onExtraValue(parsedOption.getOption().getSubcommand());
-                return new OptionParseState(parsedOption, option, nextState);
-            }
-            return new OptionParseState(parsedOption, option, this);
+        public boolean maybeStartOption(String arg) {
+            return false;
         }
 
-        OptionParseState onUnknownOption(OptionString option) {
-            throw new CommandLineArgumentException(String.format("Unknown command-line option '%s'.", option));
+        @Override
+        public OptionParserState onStartOption(String arg, String option) {
+            return new UnknownOptionParserState(arg, commandLine, this);
         }
 
         @Override
-        public ParseState onExtraValue(String arg) {
+        public ParserState onNonOption(String arg) {
             commandLine.addExtraValue(arg);
             return this;
         }
     }
 
-    private class BeforeSubcommand extends GlobalParseState {
-        BeforeSubcommand(ParsedCommandLine commandLine) {
-            super(commandLine);
+    private static class MissingOptionArgState extends ParserState {
+        private final OptionParserState option;
+
+        private MissingOptionArgState(OptionParserState option) {
+            this.option = option;
+        }
+
+        @Override
+        public boolean maybeStartOption(String arg) {
+            return isOption(arg);
+        }
+
+        @Override
+        public OptionParserState onStartOption(String arg, String option) {
+            return this.option.onComplete().onStartOption(arg, option);
+        }
+
+        @Override
+        public ParserState onNonOption(String arg) {
+            return option.onArgument(arg);
         }
 
         @Override
-        public ParseState onExtraValue(String arg) {
-            super.onExtraValue(arg);
-            return new AfterSubcommand(commandLine);
+        public void onCommandLineEnd() {
+            option.onComplete();
         }
     }
 
-    private class AfterSubcommand extends GlobalParseState {
-        AfterSubcommand(ParsedCommandLine commandLine) {
-            super(commandLine);
+    private static abstract class OptionParserState {
+        public abstract ParserState onStartNextArg();
+
+        public abstract ParserState onArgument(String argument);
+
+        public abstract boolean getHasArgument();
+
+        public abstract ParserState onComplete();
+    }
+
+    private static class KnownOptionParserState extends OptionParserState {
+        private final OptionString optionString;
+        private final CommandLineOption option;
+        private final ParsedCommandLine commandLine;
+        private final ParserState state;
+        private final List<String> values = new ArrayList<String>();
+
+        private KnownOptionParserState(OptionString optionString, CommandLineOption option, ParsedCommandLine commandLine, ParserState state) {
+            this.optionString = optionString;
+            this.option = option;
+            this.commandLine = commandLine;
+            this.state = state;
         }
 
         @Override
-        public OptionParseState addOption(OptionString option) {
-            if (allowMixedOptions) {
-                return super.addOption(option);
-            } else {
-                return onUnknownOption(option);
+        public ParserState onArgument(String argument) {
+            if (!getHasArgument()) {
+                throw new CommandLineArgumentException(String.format("Command-line option '%s' does not take an argument.", optionString));
             }
+            if (argument.length() == 0) {
+                throw new CommandLineArgumentException(String.format("An empty argument was provided for command-line option '%s'.", optionString));
+            }
+            values.add(argument);
+            return onComplete();
+        }
+
+        @Override
+        public ParserState onStartNextArg() {
+            if (option.getAllowsArguments() && values.isEmpty()) {
+                return new MissingOptionArgState(this);
+            }
+            return onComplete();
+        }
+
+        @Override
+        public boolean getHasArgument() {
+            return option.getAllowsArguments();
         }
 
         @Override
-        OptionParseState onUnknownOption(OptionString option) {
-            commandLine.addExtraValue(option.arg);
-            return new OptionParseState(new ParsedCommandLineOption(new CommandLineOption(Collections.singleton(option.option))), option, this);
+        public ParserState onComplete() {
+            if (getHasArgument() && values.isEmpty()) {
+                throw new CommandLineArgumentException(String.format("No argument was provided for command-line option '%s'.", optionString));
+            }
+            
+            ParsedCommandLineOption parsedOption = commandLine.addOption(optionString.option, option);
+            if (values.size() + parsedOption.getValues().size() > 1 && !option.getAllowsMultipleArguments()) {
+                throw new CommandLineArgumentException(String.format("Multiple arguments were provided for command-line option '%s'.", optionString));
+            }
+            for (String value : values) {
+                parsedOption.addArgument(value);
+            }
+            if (option.getSubcommand() != null) {
+                return state.onNonOption(option.getSubcommand());
+            }
+            return state;
         }
     }
 
-    private static class OptionsComplete extends GlobalParseState {
-        OptionsComplete(ParsedCommandLine commandLine) {
-            super(commandLine);
+    private static class UnknownOptionParserState extends OptionParserState {
+        private final ParserState state;
+        private final String arg;
+        private final ParsedCommandLine commandLine;
+
+        private UnknownOptionParserState(String arg, ParsedCommandLine commandLine, ParserState state) {
+            this.arg = arg;
+            this.commandLine = commandLine;
+            this.state = state;
         }
 
         @Override
-        public boolean maybeStartOption(String arg) {
+        public boolean getHasArgument() {
             return false;
         }
+
+        @Override
+        public ParserState onStartNextArg() {
+            return onComplete();
+        }
+
+        @Override
+        public ParserState onArgument(String argument) {
+            return onComplete();
+        }
+
+        @Override
+        public ParserState onComplete() {
+            commandLine.addExtraValue(arg);
+            return state;
+        }
     }
 
     private static final class OptionComparator implements Comparator<CommandLineOption> {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestMetaData.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestMetaData.java
new file mode 100644
index 0000000..99ed47a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultBuildRequestMetaData.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization;
+
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.util.Clock;
+
+public class DefaultBuildRequestMetaData implements BuildRequestMetaData {
+    private final BuildClientMetaData clientMetaData;
+    private final Clock clock;
+
+    public DefaultBuildRequestMetaData(BuildClientMetaData clientMetaData, long startTime) {
+        this.clientMetaData = clientMetaData;
+        clock = new Clock(startTime);
+    }
+
+    public DefaultBuildRequestMetaData(long startTime) {
+        this(new GradleLauncherMetaData(), startTime);
+    }
+
+    public BuildClientMetaData getClient() {
+        return clientMetaData;
+    }
+
+    public Clock getBuildTimeClock() {
+        return clock;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
index 7cd567e..1b638bd 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -23,11 +23,12 @@ import org.gradle.StartParameter;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.initialization.Settings;
 import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.internal.file.BaseDirConverter;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.configuration.ImplicitTasksConfigurer;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -51,7 +52,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
     public static final String STACKTRACE = "s";
     private static final String SYSTEM_PROP = "D";
     private static final String PROJECT_PROP = "P";
-    private static final String GRADLE_USER_HOME = "g";
+    public static final String GRADLE_USER_HOME = "g";
     private static final String EMBEDDED_SCRIPT = "e";
     private static final String CACHE = "C";
     private static final String DRY_RUN = "m";
@@ -97,14 +98,16 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         parser.option(PROFILE).hasDescription("Profiles build execution time and generates a report in the <build_dir>/reports/profile directory.");
     }
 
-    public StartParameter convert(ParsedCommandLine args) throws CommandLineArgumentException {
-        return convert(args, new StartParameter());
+    @Override
+    protected StartParameter newInstance() {
+        return new StartParameter();
     }
 
     public StartParameter convert(ParsedCommandLine options, StartParameter startParameter) throws CommandLineArgumentException {
         LoggingConfiguration loggingConfiguration = loggingConfigurationCommandLineConverter.convert(options);
         startParameter.setLogLevel(loggingConfiguration.getLogLevel());
         startParameter.setColorOutput(loggingConfiguration.isColorOutput());
+        FileResolver resolver = new BaseDirConverter(startParameter.getCurrentDir());
 
         for (String keyValueExpression : options.option(SYSTEM_PROP).getValues()) {
             String[] elements = keyValueExpression.split("=");
@@ -121,20 +124,20 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         }
 
         if (options.hasOption(PROJECT_DIR)) {
-            startParameter.setProjectDir(new File(options.option(PROJECT_DIR).getValue()));
+            startParameter.setProjectDir(resolver.resolve(options.option(PROJECT_DIR).getValue()));
         }
         if (options.hasOption(GRADLE_USER_HOME)) {
-            startParameter.setGradleUserHomeDir(new File(options.option(GRADLE_USER_HOME).getValue()));
+            startParameter.setGradleUserHomeDir(resolver.resolve(options.option(GRADLE_USER_HOME).getValue()));
         }
         if (options.hasOption(BUILD_FILE)) {
-            startParameter.setBuildFile(new File(options.option(BUILD_FILE).getValue()));
+            startParameter.setBuildFile(resolver.resolve(options.option(BUILD_FILE).getValue()));
         }
         if (options.hasOption(SETTINGS_FILE)) {
-            startParameter.setSettingsFile(new File(options.option(SETTINGS_FILE).getValue()));
+            startParameter.setSettingsFile(resolver.resolve(options.option(SETTINGS_FILE).getValue()));
         }
 
         for (String script : options.option(INIT_SCRIPT).getValues()) {
-            startParameter.addInitScript(new File(script));
+            startParameter.addInitScript(resolver.resolve(script));
         }
 
         if (options.hasOption(CACHE)) {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
index 01a7c2a..9f0ee94 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -22,7 +22,6 @@ import org.gradle.api.internal.project.GlobalServicesRegistry;
 import org.gradle.api.internal.project.IProjectFactory;
 import org.gradle.api.internal.project.ServiceRegistry;
 import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.cache.CacheRepository;
@@ -33,7 +32,6 @@ import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.profile.ProfileListener;
-import org.gradle.util.Clock;
 import org.gradle.util.WrapUtil;
 
 import java.util.Arrays;
@@ -58,7 +56,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         sharedServices = globalServices;
 
         // Start logging system
-        sharedServices.newInstance(LoggingManagerInternal.class).setLevel(LogLevel.LIFECYCLE).start();
+//        sharedServices.newInstance(LoggingManagerInternal.class).setLevel(LogLevel.LIFECYCLE).start();
 
         commandLineConverter = sharedServices.get(CommandLineConverter.class);
         tracker = new NestedBuildTracker();
@@ -74,13 +72,26 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         return commandLineConverter.convert(Arrays.asList(commandLineArgs));
     }
 
-    public GradleLauncher newInstance(String... commandLineArgs) {
-        return newInstance(createStartParameter(commandLineArgs));
+    public DefaultGradleLauncher newInstance(StartParameter startParameter) {
+        BuildRequestMetaData requestMetaData;
+        if (tracker.getCurrentBuild() != null) {
+            requestMetaData = new DefaultBuildRequestMetaData(tracker.getCurrentBuild().getServices().get(BuildClientMetaData.class), System.currentTimeMillis());
+        } else {
+            requestMetaData = new DefaultBuildRequestMetaData(System.currentTimeMillis());
+        }
+        return doNewInstance(startParameter, requestMetaData);
+    }
+
+    public DefaultGradleLauncher newInstance(StartParameter startParameter, BuildRequestMetaData requestMetaData) {
+        // This should only be used for top-level builds
+        assert tracker.getCurrentBuild() == null;
+        return doNewInstance(startParameter, requestMetaData);
     }
 
-    public GradleLauncher newInstance(StartParameter startParameter) {
-        Clock buildClock = new Clock();
+    private DefaultGradleLauncher doNewInstance(StartParameter startParameter, BuildRequestMetaData requestMetaData) {
         TopLevelBuildServiceRegistry serviceRegistry = new TopLevelBuildServiceRegistry(sharedServices, startParameter);
+        serviceRegistry.add(BuildRequestMetaData.class, requestMetaData);
+        serviceRegistry.add(BuildClientMetaData.class, requestMetaData.getClient());
         ListenerManager listenerManager = serviceRegistry.get(ListenerManager.class);
         LoggingManagerInternal loggingManager = serviceRegistry.newInstance(LoggingManagerInternal.class);
         loggingManager.setLevel(startParameter.getLogLevel());
@@ -92,13 +103,13 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
 
         listenerManager.useLogger(new TaskExecutionLogger(serviceRegistry.get(ProgressLoggerFactory.class)));
         if (tracker.getCurrentBuild() == null) {
-            listenerManager.useLogger(new BuildLogger(Logging.getLogger(BuildLogger.class), serviceRegistry.get(StyledTextOutputFactory.class), buildClock, startParameter));
+            listenerManager.useLogger(new BuildLogger(Logging.getLogger(BuildLogger.class), serviceRegistry.get(StyledTextOutputFactory.class), startParameter, requestMetaData));
         }
         listenerManager.addListener(tracker);
         listenerManager.addListener(new BuildCleanupListener(serviceRegistry));
 
         if (startParameter.isProfile()) {
-            listenerManager.addListener(new ProfileListener(buildClock.getTimeInMs()));
+            listenerManager.addListener(new ProfileListener(requestMetaData.getBuildTimeClock().getStartTime()));
         }
 
         DefaultGradle gradle = new DefaultGradle(
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncherFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
similarity index 72%
rename from subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncherFactory.java
rename to subprojects/gradle-core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
index 33462e0..aefd3dc 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncherFactory.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
@@ -13,24 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle;
+package org.gradle.initialization;
+
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
 
 /**
- * <p>A {@code GradleFactory} is responsible for creating a {@link GradleLauncher} instance for a build, from a {@link
- * StartParameter}.</p>
+ * <p>A {@code GradleFactory} is responsible for creating a {@link org.gradle.GradleLauncher} instance for a build, from a {@link
+ * org.gradle.StartParameter}.</p>
  *
  * @author Hans Dockter
  */
 public interface GradleLauncherFactory {
     /**
-     * Creates a new {@link GradleLauncher} instance for the given parameters.
+     * Creates a new {@link org.gradle.GradleLauncher} instance for the given parameters.
      *
      * @param startParameter The parameters to use for the build.
      * @return The new instance.
      */
-    GradleLauncher newInstance(StartParameter startParameter);
+    GradleLauncher newInstance(StartParameter startParameter, BuildRequestMetaData requestMetaData);
 
-    GradleLauncher newInstance(String... commandLineArgs);
+    GradleLauncher newInstance(StartParameter startParameter);
 
     StartParameter createStartParameter(String... commandLineArgs);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java
index 5d4094a..62b8ab9 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java
@@ -17,17 +17,18 @@ package org.gradle.initialization;
 
 import org.gradle.BuildAdapter;
 import org.gradle.BuildResult;
+import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.invocation.Gradle;
 
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 public class NestedBuildTracker extends BuildAdapter {
-    private final List<Gradle> buildStack = new CopyOnWriteArrayList<Gradle>();
+    private final List<GradleInternal> buildStack = new CopyOnWriteArrayList<GradleInternal>();
 
     @Override
     public void buildStarted(Gradle gradle) {
-        buildStack.add(0, gradle);
+        buildStack.add(0, (GradleInternal) gradle);
     }
 
     @Override
@@ -35,7 +36,7 @@ public class NestedBuildTracker extends BuildAdapter {
         buildStack.remove(result.getGradle());
     }
 
-    public Gradle getCurrentBuild() {
+    public GradleInternal getCurrentBuild() {
         return buildStack.isEmpty() ? null : buildStack.get(0);
     }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java
index 94c192b..c502c6e 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java
@@ -17,16 +17,17 @@ package org.gradle.initialization;
 
 import org.gradle.util.GUtil;
 
+import java.io.Serializable;
 import java.util.*;
 
-public class ParsedCommandLine {
+public class ParsedCommandLine implements Serializable {
     private final Map<String, ParsedCommandLineOption> optionsByString = new HashMap<String, ParsedCommandLineOption>();
     private final Set<String> presentOptions = new HashSet<String>();
     private final List<String> extraArguments = new ArrayList<String>();
 
     ParsedCommandLine(Iterable<CommandLineOption> options) {
         for (CommandLineOption option : options) {
-            ParsedCommandLineOption parsedOption = new ParsedCommandLineOption(option);
+            ParsedCommandLineOption parsedOption = new ParsedCommandLineOption();
             for (String optionStr : option.getOptions()) {
                 optionsByString.put(optionStr, parsedOption);
             }
@@ -38,11 +39,23 @@ public class ParsedCommandLine {
         return String.format("options: %s, extraArguments: %s", GUtil.toString(presentOptions), GUtil.toString(extraArguments));
     }
 
+    /**
+     * Returns true if the given option is present in this command-line.
+     *
+     * @param option The option, without the '-' or '--' prefix.
+     * @return true if the option is present.
+     */
     public boolean hasOption(String option) {
         option(option);
         return presentOptions.contains(option);
     }
-    
+
+    /**
+     * Returns the value of the given option.
+     *
+     * @param option The option, without the '-' or '--' prefix.
+     * @return The option. never returns null.
+     */
     public ParsedCommandLineOption option(String option) {
         ParsedCommandLineOption parsedOption = optionsByString.get(option);
         if (parsedOption == null) {
@@ -50,7 +63,7 @@ public class ParsedCommandLine {
         }
         return parsedOption;
     }
-    
+
     public List<String> getExtraArguments() {
         return extraArguments;
     }
@@ -59,12 +72,9 @@ public class ParsedCommandLine {
         extraArguments.add(value);
     }
 
-    ParsedCommandLineOption addOption(String option) {
-        ParsedCommandLineOption parsedOption = optionsByString.get(option);
-        if (parsedOption == null) {
-            return null;
-        }
-        presentOptions.addAll(parsedOption.getOption().getOptions());
+    ParsedCommandLineOption addOption(String optionStr, CommandLineOption option) {
+        ParsedCommandLineOption parsedOption = optionsByString.get(optionStr);
+        presentOptions.addAll(option.getOptions());
         return parsedOption;
     }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java
index e68b3c8..570d1c3 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java
@@ -15,20 +15,12 @@
  */
 package org.gradle.initialization;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
-public class ParsedCommandLineOption {
+public class ParsedCommandLineOption implements Serializable {
     private final List<String> values = new ArrayList<String>();
-    private final CommandLineOption option;
-
-    ParsedCommandLineOption(CommandLineOption option) {
-        this.option = option;
-    }
-
-    public CommandLineOption getOption() {
-        return option;
-    }
 
     public String getValue() {
         if (values.isEmpty()) {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java b/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
index 191c86f..bfc1d12 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
@@ -132,6 +132,26 @@ public class DefaultGradle implements GradleInternal {
         listenerManager.addListener(ProjectEvaluationListener.class, "afterEvaluate", closure);
     }
 
+    public void buildStarted(Closure closure) {
+        listenerManager.addListener(BuildListener.class, "buildStarted", closure);
+    }
+
+    public void settingsEvaluated(Closure closure) {
+        listenerManager.addListener(BuildListener.class, "settingsEvaluated", closure);
+    }
+
+    public void projectsLoaded(Closure closure) {
+        listenerManager.addListener(BuildListener.class, "projectsLoaded", closure);
+    }
+
+    public void projectsEvaluated(Closure closure) {
+        listenerManager.addListener(BuildListener.class, "projectsEvaluated", closure);
+    }
+
+    public void buildFinished(Closure closure) {
+        listenerManager.addListener(BuildListener.class, "buildFinished", closure);
+    }
+
     public void addListener(Object listener) {
         listenerManager.addListener(listener);
     }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
similarity index 58%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
index 6f73440..95afff0 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
@@ -1,28 +1,35 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.remote.ConnectEvent;
-
-import java.net.URI;
-
-public interface MultiChannelConnector {
-    URI accept(Action<ConnectEvent<MultiChannelConnection<Message>>> action);
-
-    MultiChannelConnection<Message> connect(URI destinationAddress);
-}
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.listener;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+
+public class ActionBroadcast<T> implements Action<T> {
+    private final ListenerBroadcast<Action> broadcast = new ListenerBroadcast<Action>(Action.class);
+
+    public void execute(T t) {
+        broadcast.getSource().execute(t);
+    }
+
+    public void add(Action<? super T> action) {
+        broadcast.add(action);
+    }
+
+    public void add(Closure action) {
+        broadcast.add("execute", action);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
index fa6a29a..cc29fba 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
@@ -41,8 +41,12 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
         logLevelMap.put("", LogLevel.LIFECYCLE);
     }
 
-    public LoggingConfiguration convert(ParsedCommandLine commandLine) throws CommandLineArgumentException {
-        LoggingConfiguration loggingConfiguration = new LoggingConfiguration();
+    @Override
+    protected LoggingConfiguration newInstance() {
+        return new LoggingConfiguration();
+    }
+
+    public LoggingConfiguration convert(ParsedCommandLine commandLine, LoggingConfiguration loggingConfiguration) throws CommandLineArgumentException {
         loggingConfiguration.setLogLevel(getLogLevel(commandLine));
         if (commandLine.hasOption(NO_COLOR)) {
             loggingConfiguration.setColorOutput(false);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java
index 809bbc3..f4bc91a 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java
@@ -17,9 +17,9 @@ package org.gradle.messaging.remote.internal;
 
 public class ChannelMessage extends Message {
     private final Object channel;
-    private final Message payload;
+    private final Object payload;
 
-    public ChannelMessage(Object channel, Message payload) {
+    public ChannelMessage(Object channel, Object payload) {
         this.channel = channel;
         this.payload = payload;
     }
@@ -28,7 +28,7 @@ public class ChannelMessage extends Message {
         return channel;
     }
 
-    public Message getPayload() {
+    public Object getPayload() {
         return payload;
     }
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
index 184a39e..cd96907 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
@@ -21,15 +21,15 @@ import org.gradle.messaging.dispatch.Dispatch;
 import java.util.HashMap;
 import java.util.Map;
 
-public class ChannelMessageMarshallingDispatch implements Dispatch<Message> {
-    private final Dispatch<Message> dispatch;
+public class ChannelMessageMarshallingDispatch implements Dispatch<Object> {
+    private final Dispatch<Object> dispatch;
     private final Map<Object, Integer> channels = new HashMap<Object, Integer>();
 
-    public ChannelMessageMarshallingDispatch(Dispatch<Message> dispatch) {
+    public ChannelMessageMarshallingDispatch(Dispatch<Object> dispatch) {
         this.dispatch = dispatch;
     }
 
-    public void dispatch(Message message) {
+    public void dispatch(Object message) {
         if (message instanceof ChannelMessage) {
             ChannelMessage channelMessage = (ChannelMessage) message;
             Object key = channelMessage.getChannel();
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
index d1d1d6f..53ee640 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
@@ -21,15 +21,15 @@ import org.gradle.messaging.dispatch.Dispatch;
 import java.util.HashMap;
 import java.util.Map;
 
-public class ChannelMessageUnmarshallingDispatch implements Dispatch<Message> {
-    private final Dispatch<Message> dispatch;
+public class ChannelMessageUnmarshallingDispatch implements Dispatch<Object> {
+    private final Dispatch<Object> dispatch;
     private final Map<Integer, Object> channels = new HashMap<Integer, Object>();
 
-    public ChannelMessageUnmarshallingDispatch(Dispatch<Message> dispatch) {
+    public ChannelMessageUnmarshallingDispatch(Dispatch<Object> dispatch) {
         this.dispatch = dispatch;
     }
 
-    public void dispatch(Message message) {
+    public void dispatch(Object message) {
         if (message instanceof ChannelMetaInfo) {
             ChannelMetaInfo metaInfo = (ChannelMetaInfo) message;
             channels.put(metaInfo.getChannelId(), metaInfo.getChannelKey());
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java
similarity index 75%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
copy to subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java
index 0647de2..9b4dc7d 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java
@@ -15,11 +15,10 @@
  */
 package org.gradle.messaging.remote.internal;
 
-import java.net.URI;
+import org.gradle.api.GradleException;
 
-public interface OutgoingConnector {
-    /**
-     * Creates a connection to the given address.
-     */
-    Connection<Message> connect(URI destinationUri);
+public class ConnectException extends GradleException {
+    public ConnectException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
index 6e8f711..ceb66fe 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
@@ -24,7 +24,7 @@ public class DefaultMessagingClient implements MessagingClient {
     private final ObjectConnection connection;
 
     public DefaultMessagingClient(MultiChannelConnector connector, ClassLoader classLoader, URI serverAddress) {
-        MultiChannelConnection<Message> connection = connector.connect(serverAddress);
+        MultiChannelConnection<Object> connection = connector.connect(serverAddress);
         IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(classLoader, connection);
         OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(connection);
         this.connection = new DefaultObjectConnection(connection, connection, outgoing, incoming);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
index b6dc37a..61f3609 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
@@ -39,16 +39,16 @@ public class DefaultMessagingServer implements MessagingServer {
     }
 
     public URI accept(final Action<ConnectEvent<ObjectConnection>> action) {
-        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Message>>>() {
-            public void execute(ConnectEvent<MultiChannelConnection<Message>> connectEvent) {
+        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Object>>>() {
+            public void execute(ConnectEvent<MultiChannelConnection<Object>> connectEvent) {
                 finishConnect(connectEvent, action);
             }
         });
     }
 
-    private void finishConnect(ConnectEvent<MultiChannelConnection<Message>> connectEvent,
+    private void finishConnect(ConnectEvent<MultiChannelConnection<Object>> connectEvent,
                                Action<ConnectEvent<ObjectConnection>> action) {
-        MultiChannelConnection<Message> messageConnection = connectEvent.getConnection();
+        MultiChannelConnection<Object> messageConnection = connectEvent.getConnection();
         IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(classLoader, messageConnection);
         OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(messageConnection);
         AtomicReference<ObjectConnection> connectionRef = new AtomicReference<ObjectConnection>();
@@ -72,10 +72,10 @@ public class DefaultMessagingServer implements MessagingServer {
     }
 
     private class ConnectionAsyncStoppable implements AsyncStoppable {
-        private final MultiChannelConnection<Message> messageConnection;
+        private final MultiChannelConnection<Object> messageConnection;
         private final AtomicReference<ObjectConnection> connectionRef;
 
-        public ConnectionAsyncStoppable(MultiChannelConnection<Message> messageConnection,
+        public ConnectionAsyncStoppable(MultiChannelConnection<Object> messageConnection,
                                         AtomicReference<ObjectConnection> connectionRef) {
             this.messageConnection = messageConnection;
             this.connectionRef = connectionRef;
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
index c8feabb..1ffa534 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
@@ -28,18 +28,18 @@ import java.util.concurrent.*;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
+class DefaultMultiChannelConnection implements MultiChannelConnection<Object> {
     private final URI sourceAddress;
     private final URI destinationAddress;
     private final EndOfStreamDispatch outgoingDispatch;
-    private final AsyncDispatch<Message> outgoingQueue;
-    private final AsyncReceive<Message> incomingReceive;
+    private final AsyncDispatch<Object> outgoingQueue;
+    private final AsyncReceive<Object> incomingReceive;
     private final EndOfStreamFilter incomingDispatch;
     private final IncomingDemultiplex incomingDemux;
     private final StoppableExecutor executor;
-    private final Connection<Message> connection;
+    private final Connection<Object> connection;
 
-    DefaultMultiChannelConnection(ExecutorFactory executorFactory, String displayName, final Connection<Message> connection, URI sourceAddress, URI destinationAddress) {
+    DefaultMultiChannelConnection(ExecutorFactory executorFactory, String displayName, final Connection<Object> connection, URI sourceAddress, URI destinationAddress) {
         this.connection = connection;
         this.executor = executorFactory.create(displayName);
 
@@ -47,7 +47,7 @@ class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
         this.destinationAddress = destinationAddress;
 
         // Outgoing pipeline: <source> -> <channel-mux> -> <end-of-stream-dispatch> -> <async-queue> -> <ignore-failures> -> <connection>
-        outgoingQueue = new AsyncDispatch<Message>(executor);
+        outgoingQueue = new AsyncDispatch<Object>(executor);
         outgoingQueue.dispatchTo(wrapFailures(connection));
         outgoingDispatch = new EndOfStreamDispatch(new ChannelMessageMarshallingDispatch(outgoingQueue));
 
@@ -58,13 +58,12 @@ class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
                 requestStop();
             }
         });
-        incomingReceive = new AsyncReceive<Message>(executor, wrapFailures(new ChannelMessageUnmarshallingDispatch(incomingDispatch)));
+        incomingReceive = new AsyncReceive<Object>(executor, wrapFailures(new ChannelMessageUnmarshallingDispatch(incomingDispatch)));
         incomingReceive.receiveFrom(new EndOfStreamReceive(connection));
     }
 
-    private Dispatch<Message> wrapFailures(Dispatch<Message> dispatch) {
-        return new DiscardOnFailureDispatch<Message>(dispatch, LoggerFactory.getLogger(
-                DefaultMultiChannelConnector.class));
+    private Dispatch<Object> wrapFailures(Dispatch<Object> dispatch) {
+        return new DiscardOnFailureDispatch<Object>(dispatch, LoggerFactory.getLogger(DefaultMultiChannelConnector.class));
     }
 
     public URI getLocalAddress() {
@@ -85,11 +84,11 @@ class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
         outgoingDispatch.stop();
     }
 
-    public void addIncomingChannel(Object channelKey, Dispatch<Message> dispatch) {
+    public void addIncomingChannel(Object channelKey, Dispatch<Object> dispatch) {
         incomingDemux.addIncomingChannel(channelKey, wrapFailures(dispatch));
     }
 
-    public Dispatch<Message> addOutgoingChannel(Object channelKey) {
+    public Dispatch<Object> addOutgoingChannel(Object channelKey) {
         return new OutgoingMultiplex(channelKey, outgoingDispatch);
     }
 
@@ -113,29 +112,29 @@ class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
         }
     }
 
-    private class IncomingDemultiplex implements Dispatch<Message>, Stoppable {
+    private class IncomingDemultiplex implements Dispatch<Object>, Stoppable {
         private final Lock queueLock = new ReentrantLock();
-        private final Map<Object, AsyncDispatch<Message>> incomingQueues
-                = new HashMap<Object, AsyncDispatch<Message>>();
+        private final Map<Object, AsyncDispatch<Object>> incomingQueues
+                = new HashMap<Object, AsyncDispatch<Object>>();
 
-        public void dispatch(Message message) {
+        public void dispatch(Object message) {
             ChannelMessage channelMessage = (ChannelMessage) message;
-            Dispatch<Message> channel = findChannel(channelMessage.getChannel());
+            Dispatch<Object> channel = findChannel(channelMessage.getChannel());
             channel.dispatch(channelMessage.getPayload());
         }
 
-        public void addIncomingChannel(Object channel, Dispatch<Message> dispatch) {
-            AsyncDispatch<Message> queue = findChannel(channel);
+        public void addIncomingChannel(Object channel, Dispatch<Object> dispatch) {
+            AsyncDispatch<Object> queue = findChannel(channel);
             queue.dispatchTo(dispatch);
         }
 
-        private AsyncDispatch<Message> findChannel(Object channel) {
-            AsyncDispatch<Message> queue;
+        private AsyncDispatch<Object> findChannel(Object channel) {
+            AsyncDispatch<Object> queue;
             queueLock.lock();
             try {
                 queue = incomingQueues.get(channel);
                 if (queue == null) {
-                    queue = new AsyncDispatch<Message>(executor);
+                    queue = new AsyncDispatch<Object>(executor);
                     incomingQueues.put(channel, queue);
                 }
             } finally {
@@ -158,16 +157,16 @@ class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
         }
     }
 
-    private static class OutgoingMultiplex implements Dispatch<Message> {
-        private final Dispatch<Message> dispatch;
+    private static class OutgoingMultiplex implements Dispatch<Object> {
+        private final Dispatch<Object> dispatch;
         private final Object channelKey;
 
-        private OutgoingMultiplex(Object channelKey, Dispatch<Message> dispatch) {
+        private OutgoingMultiplex(Object channelKey, Dispatch<Object> dispatch) {
             this.channelKey = channelKey;
             this.dispatch = dispatch;
         }
 
-        public void dispatch(Message message) {
+        public void dispatch(Object message) {
             dispatch.dispatch(new ChannelMessage(channelKey, message));
         }
     }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
index 9171c75..4c25098 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
@@ -42,27 +42,26 @@ public class DefaultMultiChannelConnector implements MultiChannelConnector, Stop
         executorService.stop();
     }
 
-    public URI accept(final Action<ConnectEvent<MultiChannelConnection<Message>>> action) {
-        return incomingConnector.accept(new Action<ConnectEvent<Connection<Message>>>() {
-            public void execute(ConnectEvent<Connection<Message>> event) {
+    public URI accept(final Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
+        return incomingConnector.accept(new Action<ConnectEvent<Connection<Object>>>() {
+            public void execute(ConnectEvent<Connection<Object>> event) {
                 finishConnect(event, action);
             }
         });
     }
 
-    private void finishConnect(ConnectEvent<Connection<Message>> event,
-                               Action<ConnectEvent<MultiChannelConnection<Message>>> action) {
+    private void finishConnect(ConnectEvent<Connection<Object>> event,
+                               Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
         URI localAddress = event.getLocalAddress();
         URI remoteAddress = event.getRemoteAddress();
         DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(executorFactory,
                 String.format("Incoming Connection %s", localAddress), event.getConnection(), localAddress, remoteAddress);
-        action.execute(new ConnectEvent<MultiChannelConnection<Message>>(channelConnection, localAddress, remoteAddress));
+        action.execute(new ConnectEvent<MultiChannelConnection<Object>>(channelConnection, localAddress, remoteAddress));
     }
 
-    public MultiChannelConnection<Message> connect(URI destinationAddress) {
-        Connection<Message> connection = outgoingConnector.connect(destinationAddress);
-        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(executorFactory,
+    public MultiChannelConnection<Object> connect(URI destinationAddress) {
+        Connection<Object> connection = outgoingConnector.connect(destinationAddress);
+        return new DefaultMultiChannelConnection(executorFactory,
                 String.format("Outgoing Connection %s", destinationAddress), connection, null, destinationAddress);
-        return channelConnection;
     }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
index 98b5021..ff977e1 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
@@ -22,16 +22,16 @@ import org.gradle.messaging.dispatch.StoppableDispatch;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-public class EndOfStreamDispatch implements StoppableDispatch<Message> {
-    private final Dispatch<? super Message> dispatch;
+public class EndOfStreamDispatch implements StoppableDispatch<Object> {
+    private final Dispatch<Object> dispatch;
     private boolean stopped;
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
 
-    public EndOfStreamDispatch(Dispatch<? super Message> dispatch) {
+    public EndOfStreamDispatch(Dispatch<Object> dispatch) {
         this.dispatch = dispatch;
     }
 
-    public void dispatch(Message message) {
+    public void dispatch(Object message) {
         lock.readLock().lock();
         try {
             if (stopped) {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
index de6b857..1a3713e 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
@@ -24,19 +24,19 @@ import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-public class EndOfStreamFilter implements Dispatch<Message>, Stoppable {
-    private final Dispatch<Message> dispatch;
+public class EndOfStreamFilter implements Dispatch<Object>, Stoppable {
+    private final Dispatch<Object> dispatch;
     private final Runnable endOfStreamAction;
     private final Lock lock = new ReentrantLock();
     private final Condition condition = lock.newCondition();
     private boolean endOfStreamReached;
 
-    public EndOfStreamFilter(Dispatch<Message> dispatch, Runnable endOfStreamAction) {
+    public EndOfStreamFilter(Dispatch<Object> dispatch, Runnable endOfStreamAction) {
         this.dispatch = dispatch;
         this.endOfStreamAction = endOfStreamAction;
     }
 
-    public void dispatch(Message message) {
+    public void dispatch(Object message) {
         lock.lock();
         try {
             if (endOfStreamReached) {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java
index 25a720a..7f40d09 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java
@@ -18,19 +18,19 @@ package org.gradle.messaging.remote.internal;
 
 import org.gradle.messaging.dispatch.Receive;
 
-class EndOfStreamReceive implements Receive<Message> {
-    private final Receive<Message> receive;
+class EndOfStreamReceive implements Receive<Object> {
+    private final Receive<Object> receive;
     private boolean ended;
 
-    public EndOfStreamReceive(Receive<Message> receive) {
+    public EndOfStreamReceive(Receive<Object> receive) {
         this.receive = receive;
     }
 
-    public Message receive() {
+    public Object receive() {
         if (ended) {
             return null;
         }
-        Message message = receive.receive();
+        Object message = receive.receive();
         if (message == null) {
             ended = true;
             return new EndOfStreamEvent();
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
index 79fec45..a4f793d 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
@@ -32,14 +32,14 @@ public class HandshakeIncomingConnector implements IncomingConnector {
     private final Object lock = new Object();
     private URI localAddress;
     private long nextId;
-    private final Map<URI, Action<ConnectEvent<Connection<Message>>>> pendingActions = new HashMap<URI, Action<ConnectEvent<Connection<Message>>>>();
+    private final Map<URI, Action<ConnectEvent<Connection<Object>>>> pendingActions = new HashMap<URI, Action<ConnectEvent<Connection<Object>>>>();
 
     public HandshakeIncomingConnector(IncomingConnector connector, Executor executor) {
         this.connector = connector;
         this.executor = executor;
     }
 
-    public URI accept(Action<ConnectEvent<Connection<Message>>> action) {
+    public URI accept(Action<ConnectEvent<Connection<Object>>> action) {
         synchronized (lock) {
             if (localAddress == null) {
                 localAddress = connector.accept(handShakeAction());
@@ -56,9 +56,9 @@ public class HandshakeIncomingConnector implements IncomingConnector {
         }
     }
 
-    private Action<ConnectEvent<Connection<Message>>> handShakeAction() {
-        return new Action<ConnectEvent<Connection<Message>>>() {
-            public void execute(final ConnectEvent<Connection<Message>> connectEvent) {
+    private Action<ConnectEvent<Connection<Object>>> handShakeAction() {
+        return new Action<ConnectEvent<Connection<Object>>>() {
+            public void execute(final ConnectEvent<Connection<Object>> connectEvent) {
                 executor.execute(new Runnable() {
                     public void run() {
                         handshake(connectEvent);
@@ -68,11 +68,11 @@ public class HandshakeIncomingConnector implements IncomingConnector {
         };
     }
 
-    private void handshake(ConnectEvent<Connection<Message>> connectEvent) {
-        Connection<Message> connection = connectEvent.getConnection();
+    private void handshake(ConnectEvent<Connection<Object>> connectEvent) {
+        Connection<Object> connection = connectEvent.getConnection();
         ConnectRequest request = (ConnectRequest) connection.receive();
         URI localAddress = request.getDestinationAddress();
-        Action<ConnectEvent<Connection<Message>>> channelConnection;
+        Action<ConnectEvent<Connection<Object>>> channelConnection;
         synchronized (lock) {
             channelConnection = pendingActions.remove(localAddress);
         }
@@ -80,6 +80,6 @@ public class HandshakeIncomingConnector implements IncomingConnector {
             throw new IllegalStateException(String.format(
                     "Request to connect received for unknown address '%s'.", localAddress));
         }
-        channelConnection.execute(new ConnectEvent<Connection<Message>>(connection, localAddress, connectEvent.getRemoteAddress()));
+        channelConnection.execute(new ConnectEvent<Connection<Object>>(connection, localAddress, connectEvent.getRemoteAddress()));
     }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
index 7553624..42e8fef 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
@@ -25,8 +25,8 @@ public interface IncomingConnector {
     /**
      * Allocates a new incoming endpoint.
      *
-     * @param action the action to execute on incoming connection.
+     * @param action the action to execute on incoming connection. The supplied action is not required to be thread-safe.
      * @return the address of the endpoint which the connector is listening on.
      */
-    URI accept(Action<ConnectEvent<Connection<Message>>> action);
+    URI accept(Action<ConnectEvent<Connection<Object>>> action);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
index a3d0065..652b422 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
@@ -25,10 +25,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
 
 public class IncomingMethodInvocationHandler {
     private final ClassLoader classLoader;
-    private final MultiChannelConnection<Message> connection;
+    private final MultiChannelConnection<Object> connection;
     private final Set<Class<?>> classes = new CopyOnWriteArraySet<Class<?>>();
 
-    public IncomingMethodInvocationHandler(ClassLoader classLoader, MultiChannelConnection<Message> connection) {
+    public IncomingMethodInvocationHandler(ClassLoader classLoader, MultiChannelConnection<Object> connection) {
         this.classLoader = classLoader;
         this.connection = connection;
     }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
index d0dbf9a..0501333 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
@@ -23,19 +23,19 @@ import java.io.*;
 import java.lang.reflect.Constructor;
 
 public abstract class Message implements Serializable {
-    public void send(OutputStream outputSteam) throws IOException {
+    public static void send(Object message, OutputStream outputSteam) throws IOException {
         ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
         try {
-            oos.writeObject(this);
+            oos.writeObject(message);
         } finally {
             oos.flush();
         }
     }
 
-    public static Message receive(InputStream inputSteam, ClassLoader classLoader)
+    public static Object receive(InputStream inputSteam, ClassLoader classLoader)
             throws IOException, ClassNotFoundException {
         ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
-        return (Message) ois.readObject();
+        return ois.readObject();
     }
 
     private static class ExceptionPlaceholder implements Serializable {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
index 231aabb..2171e07 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
@@ -22,7 +22,7 @@ import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.Map;
 
-public class MethodInvocationUnmarshallingDispatch implements Dispatch<Message> {
+public class MethodInvocationUnmarshallingDispatch implements Dispatch<Object> {
     private final Dispatch<? super MethodInvocation> dispatch;
     private final ClassLoader classLoader;
     private final Map<Object, Method> methods = new HashMap<Object, Method>();
@@ -32,7 +32,7 @@ public class MethodInvocationUnmarshallingDispatch implements Dispatch<Message>
         this.classLoader = classLoader;
     }
 
-    public void dispatch(Message message) {
+    public void dispatch(Object message) {
         if (message instanceof MethodMetaInfo) {
             MethodMetaInfo methodMetaInfo = (MethodMetaInfo) message;
             Method method = methodMetaInfo.findMethod(classLoader);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
index 6f73440..28c0f1f 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
@@ -22,7 +22,7 @@ import org.gradle.messaging.remote.ConnectEvent;
 import java.net.URI;
 
 public interface MultiChannelConnector {
-    URI accept(Action<ConnectEvent<MultiChannelConnection<Message>>> action);
+    URI accept(Action<ConnectEvent<MultiChannelConnection<Object>>> action);
 
-    MultiChannelConnection<Message> connect(URI destinationAddress);
+    MultiChannelConnection<Object> connect(URI destinationAddress);
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
index 0647de2..a0a575a 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
@@ -20,6 +20,7 @@ import java.net.URI;
 public interface OutgoingConnector {
     /**
      * Creates a connection to the given address.
+     * @throws org.gradle.messaging.remote.internal.ConnectException when there is nothing listening on the remote URI
      */
-    Connection<Message> connect(URI destinationUri);
+    <T> Connection<T> connect(URI destinationUri) throws ConnectException;
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
index 9ffec32..cedd291 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
@@ -26,9 +26,9 @@ import java.util.concurrent.ConcurrentHashMap;
 
 public class OutgoingMethodInvocationHandler {
     private final Map<Class<?>, ProxyDispatchAdapter<?>> outgoing = new ConcurrentHashMap<Class<?>, ProxyDispatchAdapter<?>>();
-    private final MultiChannelConnection<Message> connection;
+    private final MultiChannelConnection<Object> connection;
 
-    public OutgoingMethodInvocationHandler(MultiChannelConnection<Message> connection) {
+    public OutgoingMethodInvocationHandler(MultiChannelConnection<Object> connection) {
         this.connection = connection;
     }
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
index 7e2fa3b..64cde17 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
@@ -24,21 +24,20 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.nio.channels.SocketChannel;
 
-public class SocketConnection implements Connection<Message> {
+public class SocketConnection<T> implements Connection<T> {
     private final SocketChannel socket;
-    private final URI localAddress;
-    private final URI remoteAddress;
+    private final Object localAddress;
+    private final Object remoteAddress;
     private final ClassLoader classLoader;
     private final InputStream instr;
     private final OutputStream outstr;
 
-    public SocketConnection(SocketChannel socket, URI localAddress, URI remoteAddress, ClassLoader classLoader) {
+    public SocketConnection(SocketChannel socket, Object localAddress, Object remoteAddress, ClassLoader classLoader) {
         this.socket = socket;
         this.localAddress = localAddress;
         this.remoteAddress = remoteAddress;
@@ -59,9 +58,9 @@ public class SocketConnection implements Connection<Message> {
         return String.format("socket connection at %s with %s", localAddress, remoteAddress);
     }
 
-    public Message receive() {
+    public T receive() {
         try {
-            return Message.receive(instr, classLoader);
+            return (T) Message.receive(instr, classLoader);
         } catch (Exception e) {
             if (isEndOfStream(e)) {
                 return null;
@@ -80,9 +79,9 @@ public class SocketConnection implements Connection<Message> {
         return false;
     }
 
-    public void dispatch(Message message) {
+    public void dispatch(T message) {
         try {
-            message.send(outstr);
+            Message.send(message, outstr);
             outstr.flush();
         } catch (Exception e) {
             throw new GradleException(String.format("Could not write message to '%s'.", remoteAddress), e);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
index bedc339..1ceca32 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
@@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
-import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.util.List;
@@ -49,7 +49,7 @@ public class TcpIncomingConnector implements IncomingConnector, AsyncStoppable {
         localAddresses = TcpOutgoingConnector.findLocalAddresses();
     }
 
-    public URI accept(Action<ConnectEvent<Connection<Message>>> action) {
+    public URI accept(Action<ConnectEvent<Connection<Object>>> action) {
         ServerSocketChannel serverSocket;
         URI localAddress;
         try {
@@ -78,9 +78,9 @@ public class TcpIncomingConnector implements IncomingConnector, AsyncStoppable {
     private class Receiver implements Runnable {
         private final ServerSocketChannel serverSocket;
         private final URI localAddress;
-        private final Action<ConnectEvent<Connection<Message>>> action;
+        private final Action<ConnectEvent<Connection<Object>>> action;
 
-        public Receiver(ServerSocketChannel serverSocket, URI localAddress, Action<ConnectEvent<Connection<Message>>> action) {
+        public Receiver(ServerSocketChannel serverSocket, URI localAddress, Action<ConnectEvent<Connection<Object>>> action) {
             this.serverSocket = serverSocket;
             this.localAddress = localAddress;
             this.action = action;
@@ -96,9 +96,9 @@ public class TcpIncomingConnector implements IncomingConnector, AsyncStoppable {
                     }
                     URI remoteUri = new URI(String.format("tcp://localhost:%d", remoteAddress.getPort()));
                     LOGGER.debug("Accepted connection from {}.", remoteUri);
-                    action.execute(new ConnectEvent<Connection<Message>>(new SocketConnection(socket, localAddress, remoteUri, classLoader), localAddress, remoteUri));
+                    action.execute(new ConnectEvent<Connection<Object>>(new SocketConnection<Object>(socket, localAddress, remoteUri, classLoader), localAddress, remoteUri));
                 }
-            } catch (AsynchronousCloseException e) {
+            } catch (ClosedChannelException e) {
                 // Ignore
             } catch (Exception e) {
                 LOGGER.error("Could not accept remote connection.", e);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
index 2c1de4a..17e5137 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
@@ -48,7 +48,7 @@ public class TcpMessagingClient implements MessagingClient {
     }
 
     private static class NoOpIncomingConnector implements IncomingConnector {
-        public URI accept(Action<ConnectEvent<Connection<Message>>> action) {
+        public URI accept(Action<ConnectEvent<Connection<Object>>> action) {
             throw new UnsupportedOperationException();
         }
     }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java
index b60b8fa..8b167c5 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java
@@ -34,8 +34,8 @@ public class TcpOutgoingConnector implements OutgoingConnector {
         this.classLoader = classLoader;
     }
 
-    public Connection<Message> connect(URI destinationUri) {
-        if (!destinationUri.getScheme().equals("tcp") || !destinationUri.getHost().equals("localhost")) {
+    public <T> Connection<T> connect(URI destinationUri) {
+        if (!destinationUri.getScheme().equals("tcp") || destinationUri.getHost() == null || !destinationUri.getHost().equals("localhost")) {
             throw new IllegalArgumentException(String.format("Cannot create connection to destination URI '%s'.",
                     destinationUri));
         }
@@ -59,9 +59,12 @@ public class TcpOutgoingConnector implements OutgoingConnector {
                 }
                 LOGGER.debug("Connected to address {}.", address);
                 URI localAddress = new URI(String.format("tcp://localhost:%d", socketChannel.socket().getLocalPort()));
-                return new SocketConnection(socketChannel, localAddress, destinationUri, classLoader);
+                return new SocketConnection<T>(socketChannel, localAddress, destinationUri, classLoader);
             }
             throw lastFailure;
+        } catch (java.net.ConnectException e) {
+            throw new ConnectException(String.format("Could not connect to server %s. Tried addresses: %s.",
+                    destinationUri, loopBackAddresses), e);
         } catch (Exception e) {
             throw new GradleException(String.format("Could not connect to server %s. Tried addresses: %s.",
                     destinationUri, loopBackAddresses), e);
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/profile/ProfileListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/profile/ProfileListener.java
index 6fb78ce..4df4563 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/profile/ProfileListener.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/profile/ProfileListener.java
@@ -27,12 +27,9 @@ import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 
 public class ProfileListener implements BuildListener, ProjectEvaluationListener, TaskExecutionListener {
     private BuildProfile buildProfile;
-    private Map<Project, ProjectProfile> projects = new HashMap<Project, ProjectProfile>();
     private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
     private long profileStarted;
 
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
index 879c0cf..bc45b12 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
@@ -26,10 +26,8 @@ import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.cache.AutoCloseCacheFactory;
 import org.gradle.cache.CacheFactory;
 import org.gradle.cache.DefaultCacheFactory;
-import org.gradle.initialization.ClassLoaderFactory;
-import org.gradle.initialization.DefaultClassLoaderFactory;
-import org.gradle.initialization.DefaultProjectDescriptor;
-import org.gradle.initialization.DefaultProjectDescriptorRegistry;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.initialization.*;
 import org.gradle.invocation.DefaultGradle;
 import org.gradle.listener.DefaultListenerManager;
 import org.gradle.listener.ListenerManager;
@@ -231,6 +229,10 @@ public class ProjectBuilder {
             this.homeDir = homeDir;
         }
 
+        protected BuildClientMetaData createClientMetaData() {
+            return new GradleLauncherMetaData();
+        }
+
         protected GradleDistributionLocator createGradleDistributionLocator() {
             return new GradleDistributionLocator() {
                 public File getGradleHome() {
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java
index 036167d..f9a2e37 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java
@@ -20,7 +20,7 @@ package org.gradle.util;
  * @author Hans Dockter
  */
 public class Clock {
-    long start;
+    private long start;
     private TimeProvider timeProvider;
 
     private static final long MS_PER_MINUTE = 60000;
@@ -30,11 +30,20 @@ public class Clock {
         this(new TrueTimeProvider());
     }
 
+    public Clock(long start) {
+        this(new TrueTimeProvider(), start);
+    }
+
     protected Clock(TimeProvider timeProvider) {
         this.timeProvider = timeProvider;
         reset();
     }
 
+    protected Clock(TimeProvider timeProvider, long start) {
+        this.timeProvider = timeProvider;
+        this.start = start;
+    }
+
     public String getTime() {
         StringBuffer result = new StringBuffer();
         long timeInMs = getTimeInMs();
@@ -56,4 +65,7 @@ public class Clock {
         start = timeProvider.getCurrentTime();
     }
 
+    public long getStartTime() {
+        return start;
+    }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java
index 58f75a3..f5c83c8 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java
@@ -17,14 +17,14 @@
 package org.gradle.util;
 
 import org.gradle.api.Action;
-import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ActionBroadcast;
 
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Collection;
 
 public class ObservableUrlClassLoader extends URLClassLoader {
-    private final ListenerBroadcast<Action> broadcast = new ListenerBroadcast<Action>(Action.class);
+    private final ActionBroadcast<ObservableUrlClassLoader> broadcast = new ActionBroadcast<ObservableUrlClassLoader>();
 
     public ObservableUrlClassLoader(ClassLoader parent, URL... urls) {
         super(urls, parent);
@@ -41,7 +41,7 @@ public class ObservableUrlClassLoader extends URLClassLoader {
     @Override
     public void addURL(URL url) {
         super.addURL(url);
-        broadcast.getSource().execute(this);
+        broadcast.execute(this);
     }
 
     public void addURLs(Iterable<URL> urls) {
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
index 9f00c10..bc89d49 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
@@ -15,23 +15,26 @@
  */
 package org.gradle
 
-import spock.lang.Specification
-import org.gradle.logging.internal.TestStyledTextOutput
-import org.gradle.logging.StyledTextOutputFactory
-import org.gradle.api.logging.LogLevel
+import org.gradle.StartParameter.ShowStacktrace
 import org.gradle.api.GradleException
 import org.gradle.api.LocationAwareException
-import org.gradle.StartParameter.ShowStacktrace
+import org.gradle.api.logging.LogLevel
 import org.gradle.execution.TaskSelectionException
+import org.gradle.initialization.BuildClientMetaData
+import org.gradle.logging.StyledTextOutputFactory
+import org.gradle.logging.internal.TestStyledTextOutput
+import spock.lang.Specification
 
 class BuildExceptionReporterTest extends Specification {
     final TestStyledTextOutput output = new TestStyledTextOutput()
     final StyledTextOutputFactory factory = Mock()
+    final BuildClientMetaData clientMetaData = Mock()
     final StartParameter startParameter = new StartParameter()
-    final BuildExceptionReporter reporter = new BuildExceptionReporter(factory, startParameter)
+    final BuildExceptionReporter reporter = new BuildExceptionReporter(factory, startParameter, clientMetaData)
 
     def setup() {
         _ * factory.create(BuildExceptionReporter.class, LogLevel.ERROR) >> output
+        _ * clientMetaData.describeCommand(!null, !null) >> { args -> args[0].append("[gradle ${args[1].join(' ')}]")}
     }
 
     def doesNothingWheBuildIsSuccessful() {
@@ -165,7 +168,7 @@ Run with {userinput}-s{normal} or {userinput}-d{normal} option to get more detai
 <message>
 
 * Try:
-Run {userinput}gradle tasks{normal} to get a list of available tasks.
+Run {userinput}[gradle tasks]{normal} to get a list of available tasks.
 '''
     }
 
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy
index ef2226b..1f654c3 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -35,6 +35,7 @@ import org.junit.Rule
 import org.junit.Test
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
+import org.gradle.util.SetSystemProperties
 
 /**
  * @author Hans Dockter
@@ -42,6 +43,8 @@ import static org.junit.Assert.*
 class StartParameterTest {
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule
+    public SetSystemProperties systemProperties = new SetSystemProperties()
 
     @Test public void testNewInstance() {
         StartParameter testObj = new StartParameter()
@@ -83,14 +86,10 @@ class StartParameterTest {
     }
 
     @Test public void testDefaultWithGradleUserHomeSystemProp() {
-        String gradleUserHome = "/someGradleUserHomePath"
-        System.setProperty(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY, gradleUserHome)
-        try {
-            StartParameter parameter = new StartParameter();
-            assertThat(parameter.gradleUserHomeDir, equalTo(new File(gradleUserHome)))
-        } finally {
-            System.getProperties().remove(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY)    
-        }
+        File gradleUserHome = tmpDir.file("someGradleUserHomePath")
+        System.setProperty(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY, gradleUserHome.absolutePath)
+        StartParameter parameter = new StartParameter();
+        assertThat(parameter.gradleUserHomeDir, equalTo(gradleUserHome))
     }
 
     @Test public void testSetCurrentDir() {
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/GeneratorTaskTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/GeneratorTaskTest.groovy
new file mode 100644
index 0000000..8a748e6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/GeneratorTaskTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api
+
+import org.gradle.api.tasks.GeneratorTask
+import org.gradle.util.HelperUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.generator.Generator
+
+class GeneratorTaskTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    final Generator<TestConfigurationObject> generator = Mock()
+    final File inputFile = tmpDir.file('input')
+    final File outputFile = tmpDir.file('output')
+    final GeneratorTask<TestConfigurationObject> task = HelperUtil.createTask(GeneratorTask)
+
+    def setup() {
+        task.inputFile = inputFile
+        task.outputFile = outputFile
+        task.generator = generator
+    }
+
+    def usesOutputFileAsDefaultInputFile() {
+        when:
+        task.inputFile = null
+
+        then:
+        task.inputFile == task.outputFile
+
+        when:
+        task.inputFile = inputFile
+
+        then:
+        task.inputFile == inputFile
+    }
+    
+    def mergesConfigurationWhenInputFileExists() {
+        def configObject = new TestConfigurationObject()
+        inputFile.text = 'config'
+
+        when:
+        task.generate()
+
+        then:
+        1 * generator.read(inputFile) >> configObject
+        1 * generator.configure(configObject)
+        1 * generator.write(configObject, outputFile)
+        0 * _._
+    }
+
+    def generatesConfigurationWhenInputFileDoesNotExist() {
+        def configObject = new TestConfigurationObject()
+
+        when:
+        task.generate()
+
+        then:
+        1 * generator.defaultInstance() >> configObject
+        1 * generator.configure(configObject)
+        1 * generator.write(configObject, outputFile)
+        0 * _._
+    }
+
+    def executesActionBeforeConfiguringObject() {
+        def configObject = new TestConfigurationObject()
+        Action<TestConfigurationObject> action = Mock()
+        task.beforeConfigured(action)
+
+        when:
+        task.generate()
+
+        then:
+        1 * generator.defaultInstance() >> configObject
+        1 * action.execute(configObject)
+        1 * generator.configure(configObject)
+    }
+
+    def executesActionAfterConfiguringObject() {
+        def configObject = new TestConfigurationObject()
+        Action<TestConfigurationObject> action = Mock()
+        task.whenConfigured(action)
+
+        when:
+        task.generate()
+
+        then:
+        1 * generator.defaultInstance() >> configObject
+        1 * generator.configure(configObject)
+        1 * action.execute(configObject)
+    }
+}
+
+class TestConfigurationObject {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
index ebf2c7d..40867c5 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
@@ -29,9 +29,10 @@ import org.junit.Test;
 import java.util.*;
 import java.util.concurrent.Callable;
 
-import static org.gradle.util.HelperUtil.*;
-import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.*;
+import static org.gradle.util.HelperUtil.TEST_CLOSURE;
+import static org.gradle.util.HelperUtil.call;
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
@@ -300,6 +301,7 @@ public abstract class AbstractClassGeneratorTest {
 
         call("{ it.conventionProperty = 'value' }", bean);
         assertThat(conventionObject.getConventionProperty(), equalTo("value"));
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
         assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
         assertThat(call("{ it.conventionMethod('value') }", bean), equalTo((Object) "[value]"));
         assertThat(call("{ it.invokeMethod('conventionMethod', 'value') }", bean), equalTo((Object) "[value]"));
@@ -314,6 +316,7 @@ public abstract class AbstractClassGeneratorTest {
 
         call("{ it.conventionProperty = 'value' }", bean);
         assertThat(conventionObject.getConventionProperty(), equalTo("value"));
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
         assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
         assertThat(call("{ it.conventionMethod('value') }", bean), equalTo((Object) "[value]"));
         assertThat(call("{ it.invokeMethod('conventionMethod', 'value') }", bean), equalTo((Object) "[value]"));
@@ -324,6 +327,7 @@ public abstract class AbstractClassGeneratorTest {
         Bean bean = generator.generate(Bean.class).newInstance();
 
         call("{ it.metaClass.getConventionProperty = { -> 'value'} }", bean);
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
         assertThat(call("{ it.getConventionProperty() }", bean), equalTo((Object) "value"));
         assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
     }
@@ -333,6 +337,7 @@ public abstract class AbstractClassGeneratorTest {
         TestDecoratedGroovyBean bean = generator.generate(TestDecoratedGroovyBean.class).newInstance();
 
         call("{ it.metaClass.getConventionProperty = { -> 'value'} }", bean);
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
         assertThat(call("{ it.getConventionProperty() }", bean), equalTo((Object) "value"));
         assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
     }
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
index 495b603..78bfc8d 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
@@ -15,14 +15,30 @@
  */
 package org.gradle.api.internal.artifacts.publish.maven.dependencies;
 
+import static java.util.Arrays.asList;
+import static org.gradle.util.WrapUtil.toMap;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.maven.model.Exclusion;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
 import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -30,15 +46,6 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.List;
-import java.util.Set;
-
-import static java.util.Arrays.asList;
-import static org.gradle.util.WrapUtil.toMap;
-import static org.gradle.util.WrapUtil.toSet;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
-
 /**
  * @author Hans Dockter
  */
@@ -85,12 +92,18 @@ public class DefaultPomDependenciesConverterTest {
     }
 
     private Configuration createNamedConfigurationStubWithDependencies(final String confName, final ModuleDependency... dependencies) {
+        return createNamedConfigurationStubWithDependencies(confName, new HashSet<ExcludeRule>(), dependencies);
+    }
+    
+    private Configuration createNamedConfigurationStubWithDependencies(final String confName, final Set<ExcludeRule> excludeRules, final ModuleDependency... dependencies) {
         final Configuration configurationStub = context.mock(Configuration.class, confName);
         context.checking(new Expectations() {{
             allowing(configurationStub).getName();
             will(returnValue(confName));
             allowing(configurationStub).getDependencies(ModuleDependency.class);
             will(returnValue(toSet(dependencies)));
+            allowing(configurationStub).getExcludeRules();
+            will(returnValue(excludeRules));            
         }});
         return configurationStub;
     }
@@ -199,7 +212,7 @@ public class DefaultPomDependenciesConverterTest {
     }
 
     @Test
-    public void convertWithConvertableExcludes() {
+    public void convertWithConvertableDependencyExcludes() {
         final Configuration someConfigurationStub = createNamedConfigurationStubWithDependencies("someConfiguration", dependency1);
         final Exclusion mavenExclude = new Exclusion();
         mavenExclude.setGroupId("a");
@@ -217,4 +230,24 @@ public class DefaultPomDependenciesConverterTest {
         assertThat(((Exclusion) mavenDependency.getExclusions().get(0)).getGroupId(), equalTo(mavenExclude.getGroupId()));
         assertThat(((Exclusion) mavenDependency.getExclusions().get(0)).getArtifactId(), equalTo(mavenExclude.getArtifactId()));
     }
+    
+    @Test
+    public void convertWithConvertableConfigurationExcludes() {
+        final Configuration someConfigurationStub = createNamedConfigurationStubWithDependencies("someConfiguration", 
+                WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule(toMap("key", "value"))), dependency1);
+        final Exclusion mavenExclude = new Exclusion();
+        mavenExclude.setGroupId("a");
+        mavenExclude.setArtifactId("b");
+        context.checking(new Expectations() {{
+           allowing(conf2ScopeMappingContainerMock).getMapping(toSet(someConfigurationStub)); will(returnValue(createMapping(compileConfStub, "compile")));
+           allowing(excludeRuleConverterMock).convert(someConfigurationStub.getExcludeRules().iterator().next()); will(returnValue(mavenExclude));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(someConfigurationStub));
+        assertEquals(1, actualMavenDependencies.size());
+        assertTrue(hasDependency(actualMavenDependencies, "org1", "name1", "rev1", null, "compile", null, false));
+        org.apache.maven.model.Dependency mavenDependency = (org.apache.maven.model.Dependency) actualMavenDependencies.get(0);
+        assertThat(mavenDependency.getExclusions().size(), equalTo(1));
+        assertThat(((Exclusion) mavenDependency.getExclusions().get(0)).getGroupId(), equalTo(mavenExclude.getGroupId()));
+        assertThat(((Exclusion) mavenDependency.getExclusions().get(0)).getArtifactId(), equalTo(mavenExclude.getArtifactId()));
+    }
 }
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
index ee02a78..89d52b2 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
@@ -25,10 +25,10 @@ import org.apache.maven.artifact.ant.Pom;
 import org.apache.maven.settings.Settings;
 import org.apache.tools.ant.Project;
 import org.codehaus.plexus.PlexusContainerException;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.*;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.AntUtil;
@@ -38,7 +38,6 @@ import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.jmock.Expectations;
-import org.jmock.api.Action;
 import org.jmock.api.Invocation;
 import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
@@ -91,13 +90,16 @@ public abstract class AbstractMavenResolverTest {
 
     @Test
     public void deployOrInstall() throws IOException, PlexusContainerException {
-        ClassifierArtifact classifierArtifact = new ClassifierArtifact("someClassifier",
-                "someType", new File("someClassifierFile"));
-        final Set<DeployableFilesInfo> testDeployableFilesInfos = WrapUtil.toSet(
-                new DeployableFilesInfo(new File("pom1.xml"), new File("artifact1.jar"), Collections.<ClassifierArtifact>emptySet()),
-                new DeployableFilesInfo(new File("pom2.xml"), new File("artifact2.jar"), WrapUtil.toSet(classifierArtifact))
+        PublishArtifact classifierArtifact = artifact(new File("classifier.jar"));
+        final DefaultMavenDeployment deployment1 = new DefaultMavenDeployment(artifact(new File("pom1.pom")), artifact(new File("artifact1.jar")), Collections.<PublishArtifact>emptySet());
+        final DefaultMavenDeployment deployment2 = new DefaultMavenDeployment(artifact(new File("pom2.pom")), artifact(new File("artifact2.jar")), WrapUtil.toSet(classifierArtifact));
+        final Set<DefaultMavenDeployment> testDefaultMavenDeployments = WrapUtil.toSet(
+                deployment1,
+                deployment2
         );
         final AttachedArtifact attachedArtifact = new AttachedArtifact();
+        final Action<MavenDeployment> action = context.mock(Action.class);
+
         context.checking(new Expectations() {
             {
                 allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getSettings();
@@ -108,25 +110,33 @@ public abstract class AbstractMavenResolverTest {
                 will(returnValue(attachedArtifact));
                 one(artifactPomContainerMock).addArtifact(TEST_ARTIFACT, TEST_JAR_FILE);
                 allowing(artifactPomContainerMock).createDeployableFilesInfos();
-                will(returnValue(testDeployableFilesInfos));
+                will(returnValue(testDefaultMavenDeployments));
+                one(action).execute(deployment1);
+                one(action).execute(deployment2);
             }
         });
+
+        getMavenResolver().beforeDeployment(action);
         getMavenResolver().publish(TEST_IVY_ARTIFACT, TEST_IVY_FILE, true);
         getMavenResolver().publish(TEST_ARTIFACT, TEST_JAR_FILE, true);
-        checkTransaction(testDeployableFilesInfos, attachedArtifact, classifierArtifact);
+        checkTransaction(testDefaultMavenDeployments, attachedArtifact, classifierArtifact);
         assertSame(mavenSettingsMock, getMavenResolver().getSettings());
     }
 
-    protected void checkTransaction(final Set<DeployableFilesInfo> deployableFilesInfos, final AttachedArtifact attachedArtifact, ClassifierArtifact classifierArtifact) throws IOException, PlexusContainerException {
+    private PublishArtifact artifact(File file) {
+        return new DefaultPublishArtifact("name", "ext", "type", null, null, file);
+    }
+
+    protected void checkTransaction(final Set<DefaultMavenDeployment> defaultMavenDeployments, final AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
         final GrabSettingsFileAction grabSettingsFileAction = new GrabSettingsFileAction();
         context.checking(new Expectations() {
             {
                 one(getInstallDeployTask()).setProject(with(any(Project.class)));
                 one(getInstallDeployTask()).setSettingsFile(with(any(File.class)));
                 will(grabSettingsFileAction);
-                for (DeployableFilesInfo deployableFilesInfo : deployableFilesInfos) {
-                    one(getInstallDeployTask()).setFile(deployableFilesInfo.getArtifactFile());
-                    one(getInstallDeployTask()).addPom(with(pomMatcher(deployableFilesInfo.getPomFile(), getInstallDeployTask().getProject())));
+                for (DefaultMavenDeployment defaultMavenDeployment : defaultMavenDeployments) {
+                    one(getInstallDeployTask()).setFile(defaultMavenDeployment.getMainArtifact().getFile());
+                    one(getInstallDeployTask()).addPom(with(pomMatcher(defaultMavenDeployment.getPomArtifact().getFile(), getInstallDeployTask().getProject())));
                     one(loggingManagerMock).captureStandardOutput(LogLevel.INFO);
                     will(returnValue(loggingManagerMock));
                     one(loggingManagerMock).start();
@@ -224,7 +234,7 @@ public abstract class AbstractMavenResolverTest {
         assertSame(pomMock, getMavenResolver().pom(testName));
     }
 
-    private static class GrabSettingsFileAction implements Action {
+    private static class GrabSettingsFileAction implements org.jmock.api.Action {
         private File settingsFile;
         private String settingsFileContent;
 
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
index 17538b8..7551396 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
@@ -21,6 +21,7 @@ import org.apache.maven.artifact.ant.RemoteRepository;
 import org.codehaus.plexus.PlexusContainer;
 import org.codehaus.plexus.PlexusContainerException;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.MavenResolver;
 import org.gradle.api.artifacts.maven.PomFilterContainer;
 import org.gradle.api.internal.Factory;
@@ -33,7 +34,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.Set;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * @author Hans Dockter
@@ -78,7 +79,7 @@ public class BaseMavenDeployerTest extends AbstractMavenResolverTest {
         mavenDeployer.setUniqueVersion(false);
     }
 
-    protected void checkTransaction(final Set<DeployableFilesInfo> deployableFilesInfos, AttachedArtifact attachedArtifact, ClassifierArtifact classifierArtifact) throws IOException, PlexusContainerException {
+    protected void checkTransaction(final Set<DefaultMavenDeployment> defaultMavenDeployments, AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
         final Set<File> protocolJars = WrapUtil.toLinkedSet(new File("jar1"), new File("jar1"));
         context.checking(new Expectations() {{
                 allowing(configurationStub).resolve();
@@ -94,7 +95,7 @@ public class BaseMavenDeployerTest extends AbstractMavenResolverTest {
                 one(deployTaskMock).addRemoteRepository(testRepository);
                 one(deployTaskMock).addRemoteSnapshotRepository(testSnapshotRepository);
         }});
-        super.checkTransaction(deployableFilesInfos, attachedArtifact, classifierArtifact);
+        super.checkTransaction(defaultMavenDeployments, attachedArtifact, classifierArtifact);
     }
 
     @Test
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
index c952097..8a347d2 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.publish.maven.deploy;
 import org.apache.maven.artifact.ant.AttachedArtifact;
 import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
 import org.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.MavenResolver;
 import org.gradle.api.artifacts.maven.PomFilterContainer;
 import org.gradle.api.internal.Factory;
@@ -59,7 +60,7 @@ public class BaseMavenInstallerTest extends AbstractMavenResolverTest {
         mavenInstaller.setInstallTaskFactory(installTaskFactoryMock);
     }
 
-    protected void checkTransaction(final Set<DeployableFilesInfo> deployableUnits, AttachedArtifact attachedArtifact, ClassifierArtifact classifierArtifact) throws IOException, PlexusContainerException {
+    protected void checkTransaction(final Set<DefaultMavenDeployment> deployableUnits, AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
         context.checking(new Expectations() {
             {
                 allowing(installTaskFactoryMock).create();
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.groovy
new file mode 100644
index 0000000..c957ace
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.publish.maven.deploy
+
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider
+import org.gradle.api.artifacts.maven.PomFilterContainer
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.gradle.api.artifacts.maven.PublishFilter
+import org.gradle.api.artifacts.maven.MavenPom
+import org.gradle.api.artifacts.PublishArtifact
+
+class DefaultArtifactPomContainerTest extends Specification {
+    final MavenPomMetaInfoProvider pomMetaInfoProvider = Mock()
+    final PomFilterContainer pomFilterContainer = Mock()
+    final ArtifactPomFactory artifactPomFactory = Mock()
+    final File pomDir = new File('pomDir')
+    final DefaultArtifactPomContainer container = new DefaultArtifactPomContainer(pomMetaInfoProvider, pomFilterContainer, artifactPomFactory)
+
+    def setup() {
+        _ * pomMetaInfoProvider.mavenPomDir >> pomDir
+    }
+    
+    def addsArtifactToFirstMatchingArtifactPom() {
+        File artifactFile = new File('artifact')
+        Artifact artifact = artifact()
+        MavenPom templatePom = Mock()
+        PomFilter filter1 = alwaysFilter('filterName', templatePom)
+        PomFilter filter2 = neverFilter()
+        ArtifactPom pom = pom('artifactId')
+        PublishArtifact pomArtifact = Mock()
+        PublishArtifact mainArtifact = Mock()
+        PublishArtifact attachArtifact = Mock()
+
+        when:
+        container.addArtifact(artifact, artifactFile)
+        def infos = container.createDeployableFilesInfos()
+
+        then:
+        _ * pomFilterContainer.activePomFilters >> [filter1, filter2]
+        _ * artifactPomFactory.createArtifactPom(templatePom) >> pom
+        _ * pom.writePom(new File(pomDir, 'pom-filterName.xml')) >> pomArtifact
+        _ * pom.artifact >> mainArtifact
+        _ * pom.attachedArtifacts >> ([attachArtifact] as Set)
+
+        infos.size() == 1
+        DefaultMavenDeployment info = infos.asList()[0]
+
+        info.pomArtifact == pomArtifact
+        info.mainArtifact == mainArtifact
+        info.artifacts == [pomArtifact, mainArtifact, attachArtifact] as Set
+        info.attachedArtifacts == [attachArtifact] as Set
+    }
+
+    def alwaysFilter(String filterName, MavenPom template) {
+        filter(filterName, true, template)
+    }
+
+    def neverFilter() {
+        filter('never', false)
+    }
+
+    def filter(String name, boolean accept, MavenPom template = null) {
+        PomFilter filter = Mock()
+        PublishFilter publishFilter = Mock()
+        _ * filter.name >> name
+        _ * filter.filter >> publishFilter
+        _ * publishFilter.accept(_, _) >> accept
+        _ * filter.pomTemplate >> template
+        filter
+    }
+
+    def artifact() {
+        Artifact artifact = Mock()
+        return artifact
+    }
+
+    def pom(String artifactId) {
+        ArtifactPom pom = Mock()
+        MavenPom mavenPom = Mock()
+        _ * pom.pom >> mavenPom
+        _ * mavenPom.artifactId >> artifactId
+        return pom
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.java
deleted file mode 100644
index 8fd0e71..0000000
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.publish.maven.deploy;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.artifacts.maven.PublishFilter;
-import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultArtifactPomContainerTest {
-    private static final File TEST_POM_DIR = new File("pomDir");
-
-    private DefaultArtifactPomContainer artifactPomContainer;
-
-    private MavenPomMetaInfoProvider metaInfoProviderMock;
-    private PomFilterContainer pomFilterContainerMock;
-    private PomFilter pomFilterMock;
-    private PublishFilter publishFilterMock;
-    private ArtifactPomFactory artifactPomFactoryMock;
-    private ArtifactPom artifactPomMock;
-    private MavenPom mavenPomMock;
-    private MavenPom mavenTemplatePomMock;
-
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private File expectedFile;
-    private File expectedPomFile;
-    private Artifact expectedArtifact;
-    private static final String POMFILTER_NAME = "somename";
-
-    @Before
-    public void setUp() {
-        expectedPomFile = new File(TEST_POM_DIR, "pom-" + POMFILTER_NAME + ".xml");
-        expectedFile = new File("somePath");
-        expectedArtifact = createTestArtifact("someName");
-        pomFilterContainerMock = context.mock(PomFilterContainer.class);
-        pomFilterMock = context.mock(PomFilter.class);
-        artifactPomMock = context.mock(ArtifactPom.class);
-        artifactPomFactoryMock = context.mock(ArtifactPomFactory.class);
-        publishFilterMock = context.mock(PublishFilter.class);
-        mavenPomMock = context.mock(MavenPom.class);
-        mavenTemplatePomMock = context.mock(MavenPom.class, "templatePom");
-        metaInfoProviderMock = context.mock(MavenPomMetaInfoProvider.class);
-
-        artifactPomContainer = new DefaultArtifactPomContainer(metaInfoProviderMock, pomFilterContainerMock,
-                artifactPomFactoryMock);
-    }
-
-    @Test
-    public void addArtifact() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterContainerMock).getActivePomFilters(); will(returnValue(WrapUtil.toList(pomFilterMock)));
-            allowing(pomFilterMock).getName(); will(returnValue(POMFILTER_NAME));
-            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
-            allowing(pomFilterMock).getPomTemplate(); will(returnValue(mavenTemplatePomMock));
-            allowing(publishFilterMock).accept(expectedArtifact, expectedFile); will(returnValue(true));
-            allowing(artifactPomFactoryMock).createArtifactPom(mavenTemplatePomMock); will(returnValue(artifactPomMock));
-            one(artifactPomMock).addArtifact(expectedArtifact, expectedFile);
-            allowing(artifactPomMock).getPom(); will(returnValue(mavenPomMock));
-            allowing(artifactPomMock).getArtifactFile(); will(returnValue(expectedFile));
-            allowing(artifactPomMock).getClassifiers(); will(returnValue(new HashSet<ClassifierArtifact>()));
-            allowing(metaInfoProviderMock).getMavenPomDir(); will(returnValue(TEST_POM_DIR));
-            one(artifactPomMock).writePom(expectedPomFile);
-        }});
-        artifactPomContainer.addArtifact(expectedArtifact, expectedFile);
-        Set<DeployableFilesInfo> deployableFilesInfos = artifactPomContainer.createDeployableFilesInfos();
-        assertEquals(1, deployableFilesInfos.size());
-        assertEquals(expectedFile, deployableFilesInfos.iterator().next().getArtifactFile());
-        assertEquals(expectedPomFile, deployableFilesInfos.iterator().next().getPomFile());
-    }
-
-    @Test
-    public void addArtifactNotAcceptedByFilter() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterContainerMock).getActivePomFilters(); will(returnValue(WrapUtil.toList(pomFilterMock)));
-            allowing(pomFilterMock).getName(); will(returnValue(POMFILTER_NAME));
-            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
-            allowing(publishFilterMock).accept(expectedArtifact, expectedFile); will(returnValue(false));
-        }});
-        artifactPomContainer.addArtifact(expectedArtifact, expectedFile);
-        assertTrue(artifactPomContainer.getArtifactPoms().isEmpty());
-    }
-
-    @Test(expected= InvalidUserDataException.class)
-    public void addArtifactWithNullArtifact() {
-        artifactPomContainer.addArtifact(null, expectedFile);
-    }
-
-    @Test(expected= InvalidUserDataException.class)
-    public void addArtifactWithNullFile() {
-        artifactPomContainer.addArtifact(expectedArtifact, null);
-    }
-
-    private Artifact createTestArtifact(String name) {
-        return new DefaultArtifact(ModuleRevisionId.newInstance("org", name, "1.0"), null, name, "jar", "jar");
-    }
-}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
index b40e5c9..221a8af 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
@@ -19,16 +19,16 @@ import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.MavenPom;
 import org.gradle.api.internal.artifacts.publish.maven.DefaultMavenPom;
 import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer;
 import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GUtil;
 import org.gradle.util.TemporaryFolder;
-import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -39,13 +39,11 @@ import org.junit.Test;
 import java.io.File;
 import java.io.FileWriter;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItem;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
 
 /**
  * @author Hans Dockter
@@ -53,8 +51,6 @@ import static org.junit.Assert.assertThat;
 public class DefaultArtifactPomTest {
     private DefaultArtifactPom artifactPom;
     private MavenPom testPom;
-    private File expectedFile;
-    private Artifact expectedArtifact;
 
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
@@ -63,8 +59,6 @@ public class DefaultArtifactPomTest {
 
     @Before
     public void setUp() {
-        expectedFile = new File("somePath");
-        expectedArtifact = createTestArtifact("someName");
         testPom = new DefaultMavenPom(context.mock(ConfigurationContainer.class), new DefaultConf2ScopeMappingContainer(),
                 context.mock(PomDependenciesConverter.class), context.mock(FileResolver.class));
         artifactPom = new DefaultArtifactPom(testPom);
@@ -72,91 +66,170 @@ public class DefaultArtifactPomTest {
 
     @Test
     public void pomWithMainArtifact() {
-        artifactPom.addArtifact(expectedArtifact, expectedFile);
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
 
-        assertEquals(expectedArtifact, artifactPom.getArtifact());
-        assertEquals(expectedFile, artifactPom.getArtifactFile());
-        checkPom(expectedArtifact.getModuleRevisionId().getOrganisation(), expectedArtifact.getName(),
-                expectedArtifact.getType(), expectedArtifact.getModuleRevisionId().getRevision());
+        artifactPom.addArtifact(mainArtifact, mainFile);
+
+        assertThat(artifactPom.getArtifact().getName(), equalTo("someName"));
+        assertThat(artifactPom.getArtifact().getExtension(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getType(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getClassifier(), nullValue());
+        assertThat(artifactPom.getArtifact().getFile(), equalTo(mainFile));
+
+        assertThat(artifactPom.getPom().getGroupId(), equalTo("org"));
+        assertThat(artifactPom.getPom().getArtifactId(), equalTo("someName"));
+        assertThat(artifactPom.getPom().getVersion(), equalTo("1.0"));
+        assertThat(artifactPom.getPom().getPackaging(), equalTo("mainPackaging"));
     }
 
     @Test
     public void pomWithMainArtifactAndClassifierArtifacts() {
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
+        Artifact classifierArtifact = createTestArtifact("otherName", "javadoc", "zip");
         File classifierFile = new File("someClassifierFile");
-        Artifact classifierArtifact = createTestArtifact(expectedArtifact.getName(), "javadoc", "zip");
 
+        artifactPom.addArtifact(mainArtifact, mainFile);
         artifactPom.addArtifact(classifierArtifact, classifierFile);
-        artifactPom.addArtifact(expectedArtifact, expectedFile);
 
-        assertThat(artifactPom.getClassifiers(),
-                hasItem(new ClassifierArtifact("javadoc", "sometype", new File("someFile"))));
+        assertThat(artifactPom.getArtifact().getName(), equalTo("someName"));
+        assertThat(artifactPom.getArtifact().getExtension(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getType(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getClassifier(), nullValue());
+        assertThat(artifactPom.getArtifact().getFile(), equalTo(mainFile));
+
+        PublishArtifact artifact = singleItem(artifactPom.getAttachedArtifacts());
+        assertThat(artifact.getName(), equalTo("someName"));
+        assertThat(artifact.getExtension(), equalTo("zip"));
+        assertThat(artifact.getType(), equalTo("zip"));
+        assertThat(artifact.getClassifier(), equalTo("javadoc"));
+        assertThat(artifact.getFile(), equalTo(classifierFile));
 
-        assertEquals(expectedArtifact, artifactPom.getArtifact());
-        assertEquals(expectedFile, artifactPom.getArtifactFile());
-        checkPom(expectedArtifact.getModuleRevisionId().getOrganisation(), expectedArtifact.getName(),
-                expectedArtifact.getType(), expectedArtifact.getModuleRevisionId().getRevision());
+        assertThat(artifactPom.getPom().getGroupId(), equalTo("org"));
+        assertThat(artifactPom.getPom().getArtifactId(), equalTo("someName"));
+        assertThat(artifactPom.getPom().getVersion(), equalTo("1.0"));
+        assertThat(artifactPom.getPom().getPackaging(), equalTo("mainPackaging"));
     }
-    
+
     @Test
-    public void pomWithClassifierArtifacts() {
+    public void pomWithClassifierArtifactsOnly() {
         File classifierFile = new File("someClassifierFile");
-        Artifact classifierArtifact = createTestArtifact(expectedArtifact.getName(), "javadoc", "zip");
+        Artifact classifierArtifact = createTestArtifact("someName", "javadoc", "zip");
 
         artifactPom.addArtifact(classifierArtifact, classifierFile);
 
-        assertThat(artifactPom.getClassifiers(),
-                hasItem(new ClassifierArtifact("javadoc", "sometype", new File("someFile"))));
-        checkPom(classifierArtifact.getModuleRevisionId().getOrganisation(),
-                classifierArtifact.getName(), "jar",
-                classifierArtifact.getModuleRevisionId().getRevision());
+        assertThat(artifactPom.getArtifact(), nullValue());
+
+        PublishArtifact artifact = singleItem(artifactPom.getAttachedArtifacts());
+        assertThat(artifact.getName(), equalTo("someName"));
+        assertThat(artifact.getExtension(), equalTo("zip"));
+        assertThat(artifact.getType(), equalTo("zip"));
+        assertThat(artifact.getClassifier(), equalTo("javadoc"));
+        assertThat(artifact.getFile(), equalTo(classifierFile));
+
+        assertThat(artifactPom.getPom().getGroupId(), equalTo("org"));
+        assertThat(artifactPom.getPom().getArtifactId(), equalTo("someName"));
+        assertThat(artifactPom.getPom().getVersion(), equalTo("1.0"));
+        assertThat(artifactPom.getPom().getPackaging(), equalTo("jar"));
     }
 
+    @Test
+    public void pomWithMainArtifactAndMetadataArtifacts() {
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
+        File metadataFile = new File("someMetadataFile");
+        Artifact metadataArtifact = createTestArtifact("otherName", null, "sometype");
+
+        artifactPom.addArtifact(mainArtifact, mainFile);
+        artifactPom.addArtifact(metadataArtifact, metadataFile);
+
+        assertThat(artifactPom.getArtifact().getName(), equalTo("someName"));
+        assertThat(artifactPom.getArtifact().getExtension(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getType(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getClassifier(), nullValue());
+        assertThat(artifactPom.getArtifact().getFile(), equalTo(mainFile));
+
+        PublishArtifact artifact = singleItem(artifactPom.getAttachedArtifacts());
+        assertThat(artifact.getName(), equalTo("someName"));
+        assertThat(artifact.getExtension(), equalTo("sometype"));
+        assertThat(artifact.getType(), equalTo("sometype"));
+        assertThat(artifact.getClassifier(), nullValue());
+        assertThat(artifact.getFile(), equalTo(metadataFile));
+
+        assertThat(artifactPom.getPom().getGroupId(), equalTo("org"));
+        assertThat(artifactPom.getPom().getArtifactId(), equalTo("someName"));
+        assertThat(artifactPom.getPom().getVersion(), equalTo("1.0"));
+        assertThat(artifactPom.getPom().getPackaging(), equalTo("mainPackaging"));
+    }
+    
     @Test(expected = InvalidUserDataException.class)
     public void addClassifierTwiceShouldThrowInvalidUserDataEx() {
         File classifierFile = new File("someClassifierFile");
-        Artifact classifierArtifact = createTestArtifact(expectedArtifact.getName(), "javadoc");
+        Artifact classifierArtifact = createTestArtifact("someName", "javadoc");
         artifactPom.addArtifact(classifierArtifact, classifierFile);
         artifactPom.addArtifact(classifierArtifact, classifierFile);
     }
 
     @Test(expected = InvalidUserDataException.class)
     public void addMainArtifactTwiceShouldThrowInvalidUserDataEx() {
-        artifactPom.addArtifact(expectedArtifact, expectedFile);
-        artifactPom.addArtifact(expectedArtifact, expectedFile);
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
+        artifactPom.addArtifact(mainArtifact, mainFile);
+        artifactPom.addArtifact(mainArtifact, mainFile);
     }
 
     @Test
-    public void initWithCustomPomSettings() {
-        testPom.setArtifactId(expectedArtifact.getName() + "X");
-        testPom.setGroupId(expectedArtifact.getModuleRevisionId().getOrganisation() + "X");
-        testPom.setVersion(expectedArtifact.getModuleRevisionId().getRevision() + "X");
-        testPom.setPackaging(expectedArtifact.getType() + "X");
-        artifactPom = new DefaultArtifactPom(testPom);
-        artifactPom.addArtifact(expectedArtifact, expectedFile);
-        assertEquals(expectedArtifact, artifactPom.getArtifact());
-        assertEquals(expectedFile, artifactPom.getArtifactFile());
-        checkPom(testPom.getGroupId(), testPom.getArtifactId(), testPom.getPackaging(), testPom.getVersion());
-    }
+    public void cannotAddMultipleArtifactsWithTheSameTypeAndClassifier() {
 
-    private void checkPom(String organisation, String name, String type, String revision) {
-        assertEquals(organisation, testPom.getGroupId());
-        assertEquals(name, testPom.getArtifactId());
-        assertEquals(type, testPom.getPackaging());
-        assertEquals(revision, testPom.getVersion());
-    }
+        // No classifier
+        Artifact mainArtifact = createTestArtifact("someName", null);
+        artifactPom.addArtifact(mainArtifact, new File("someFile"));
 
-    @Test(expected = InvalidUserDataException.class)
-    public void addArtifactWithArtifactSrcNull() {
-        new DefaultArtifactPom(testPom).addArtifact(expectedArtifact, null);
+        assertIsDuplicate(mainArtifact, new File("someFile"));
+        assertIsDuplicate(mainArtifact, new File("otherFile"));
+        assertIsDuplicate(createTestArtifact("otherName", null), new File("otherFile"));
+
+        // Classifier
+        Artifact classifierArtifact = createTestArtifact("someName", "classifier");
+        artifactPom.addArtifact(classifierArtifact, new File("classifierFile"));
+
+        assertIsDuplicate(classifierArtifact, new File("someFile"));
+        assertIsDuplicate(classifierArtifact, new File("otherFile"));
+        assertIsDuplicate(createTestArtifact("otherName", "classifier"), new File("otherFile"));
     }
 
-    @Test(expected = InvalidUserDataException.class)
-    public void addArtifactWithArtifactNull() {
-        new DefaultArtifactPom(testPom).addArtifact(null, expectedFile);
+    private void assertIsDuplicate(Artifact artifact, File file) {
+        try {
+            artifactPom.addArtifact(artifact, file);
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), startsWith("A POM cannot have multiple artifacts with the same type and classifier."));
+        }
     }
-    
-    private Artifact createTestArtifact(String name) {
-        return createTestArtifact(name, null);
+
+    @Test
+    public void initWithCustomPomSettings() {
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
+
+        testPom.setArtifactId("customArtifactId");
+        testPom.setGroupId("customGroupId");
+        testPom.setVersion("customVersion");
+        testPom.setPackaging("customPackaging");
+
+        artifactPom.addArtifact(mainArtifact, mainFile);
+
+        assertThat(artifactPom.getArtifact().getName(), equalTo("customArtifactId"));
+        assertThat(artifactPom.getArtifact().getExtension(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getType(), equalTo("mainPackaging"));
+        assertThat(artifactPom.getArtifact().getClassifier(), nullValue());
+        assertThat(artifactPom.getArtifact().getFile(), equalTo(mainFile));
+
+        assertThat(artifactPom.getPom().getGroupId(), equalTo("customGroupId"));
+        assertThat(artifactPom.getPom().getArtifactId(), equalTo("customArtifactId"));
+        assertThat(artifactPom.getPom().getVersion(), equalTo("customVersion"));
+        assertThat(artifactPom.getPom().getPackaging(), equalTo("mainPackaging"));
     }
 
     private Artifact createTestArtifact(String name, String classifier) {
@@ -176,11 +249,26 @@ public class DefaultArtifactPomTest {
         final MavenPom mavenPomMock = context.mock(MavenPom.class);
         DefaultArtifactPom artifactPom = new DefaultArtifactPom(mavenPomMock);
         final File somePomFile = new File(tmpDir.getDir(), "someDir/somePath");
-        final Set<Configuration> configurations = WrapUtil.toSet(context.mock(Configuration.class));
         context.checking(new Expectations() {{
-            one(mavenPomMock).writeTo(with(any(FileWriter.class)));       
+            allowing(mavenPomMock).getArtifactId();
+            will(returnValue("artifactId"));
+            one(mavenPomMock).writeTo(with(any(FileWriter.class)));
         }});
-        artifactPom.writePom(somePomFile);
-        assertThat(somePomFile.getParentFile().isDirectory(), equalTo(true));
+
+        PublishArtifact artifact = artifactPom.writePom(somePomFile);
+
+        assertThat(artifact.getName(), equalTo("artifactId"));
+        assertThat(artifact.getType(), equalTo("pom"));
+        assertThat(artifact.getExtension(), equalTo("pom"));
+        assertThat(artifact.getClassifier(), nullValue());
+        assertThat(artifact.getFile(), equalTo(somePomFile));
+
+        assertThat(somePomFile.isFile(), equalTo(true));
+    }
+
+    private <T> T singleItem(Iterable<? extends T> collection) {
+        List<T> elements = GUtil.addLists(collection);
+        assertThat(elements.size(), equalTo(1));
+        return elements.get(0);
     }
 }
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/IdePluginTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/IdePluginTest.groovy
new file mode 100644
index 0000000..0b5cfb4
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/IdePluginTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.plugins
+
+import spock.lang.Specification
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+import org.gradle.api.Task
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.Delete
+
+class IdePluginTest extends Specification {
+    final Project project = HelperUtil.createRootProject()
+
+    def addsLifecycleTasks() {
+        when:
+        new TestIdePlugin().apply(project)
+
+        then:
+        Task ideTask = project.tasks['testIde']
+        ideTask instanceof DefaultTask
+        ideTask.group == 'IDE'
+
+        Task cleanTask = project.tasks['cleanTestIde']
+        cleanTask instanceof DefaultTask
+        cleanTask.group == 'IDE'
+    }
+
+    def addsWorkerTask() {
+        when:
+        new TestIdePlugin().apply(project)
+
+        then:
+        Task worker = project.tasks['generateXml']
+        Task ideTask = project.tasks['testIde']
+        ideTask.taskDependencies.getDependencies(ideTask) == [worker] as Set
+
+        Task cleaner = project.tasks['cleanGenerateXml']
+        cleaner instanceof Delete
+    }
+}
+
+class TestIdePlugin extends IdePlugin {
+    @Override protected String getLifecycleTaskName() {
+        return 'testIde'
+    }
+
+    @Override protected void onApply(Project target) {
+        def worker = target.task('generateXml')
+        addWorker(worker)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
index 07a6613..f3c5ad4 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
@@ -25,6 +25,7 @@ import static org.gradle.util.Matchers.*
 import org.gradle.api.tasks.ant.AntTarget
 import java.lang.reflect.Field
 import org.apache.tools.ant.Target
+import org.apache.tools.ant.Task
 
 class DefaultAntBuilderTest {
     private final Project project = HelperUtil.createRootProject()
@@ -107,6 +108,16 @@ class DefaultAntBuilderTest {
         }
         assertThat(ant.prop, equalTo('someValue'))
     }
+
+    @Test
+    public void setsContextClassLoaderDuringExecution() {
+        ClassLoader original = Thread.currentThread().getContextClassLoader()
+        ClassLoader cl = new URLClassLoader([] as URL[])
+        Thread.currentThread().setContextClassLoader(cl)
+        ant.taskdef(name: 'test', classname: TestTask.class.getName())
+        ant.test()
+        Thread.currentThread().setContextClassLoader(original)
+    }
     
     @Test
     public void discardsTasksAfterExecution() {
@@ -124,4 +135,12 @@ class DefaultAntBuilderTest {
         List children = field.get(target)
         assertThat(children, isEmpty())
     }
-}
\ No newline at end of file
+}
+
+public class TestTask extends Task {
+    @Override
+    void execute() {
+        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(org.apache.tools.ant.Project.class.getClassLoader()))
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
index c7df0c9..7ccc7fe 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
@@ -16,10 +16,9 @@
 package org.gradle.api.internal.project.taskfactory;
 
 import org.gradle.api.*;
+import org.gradle.api.internal.AsmBackedClassGenerator;
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.DefaultTask;
 import org.gradle.api.internal.IConventionAware;
-import org.gradle.api.internal.GroovySourceGenerationBackedClassGenerator;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.tasks.ConventionValue;
@@ -27,12 +26,16 @@ import org.gradle.api.tasks.TaskInstantiationException;
 import org.gradle.util.GUtil;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
 
 /**
  * @author Hans Dockter
@@ -48,7 +51,7 @@ public class TaskFactoryTest {
 
     @Before
     public void setUp() {
-        taskFactory = new TaskFactory(new GroovySourceGenerationBackedClassGenerator());
+        taskFactory = new TaskFactory(new AsmBackedClassGenerator());
         testProject = HelperUtil.createRootProject();
         empyArgMap = new HashMap();
     }
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
index 1242f31..2e253b0 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
@@ -16,10 +16,7 @@
 package org.gradle.api.internal.tasks;
 
 import groovy.lang.Closure;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.UnknownTaskException;
+import org.gradle.api.*;
 import org.gradle.api.internal.ClassGenerator;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -123,6 +120,22 @@ public class DefaultTaskContainerTest {
     }
 
     @Test
+    public void doesNotFireRuleWhenAddingTask() {
+        Rule rule = context.mock(Rule.class);
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Task task = task("task");
+
+        container.addRule(rule);
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+
+        container.add("task");
+    }
+    
+    @Test
     public void cannotAddDuplicateTask() {
         final Task task = addTask("task");
 
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObjectGeneratorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObjectGeneratorTest.groovy
new file mode 100644
index 0000000..7ae22ab
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/PersistableConfigurationObjectGeneratorTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator
+
+import spock.lang.Specification
+
+class PersistableConfigurationObjectGeneratorTest extends Specification {
+    final PersistableConfigurationObject object = Mock()
+    final PersistableConfigurationObjectGenerator<PersistableConfigurationObject> generator = new PersistableConfigurationObjectGenerator<PersistableConfigurationObject>() {
+        PersistableConfigurationObject create() {
+            return object
+        }
+        void configure(PersistableConfigurationObject object) {
+        }
+    }
+
+    def readsObjectFromFile() {
+        File inputFile = new File('input')
+
+        when:
+        def result = generator.read(inputFile)
+
+        then:
+        result == object
+        1 * object.load(inputFile)
+        0 * _._
+    }
+
+    def createsDefaultObject() {
+        when:
+        def result = generator.defaultInstance()
+
+        then:
+        result == object
+        1 * object.loadDefaults()
+        0 * _._
+    }
+
+    def writesObjectToFile() {
+        File outputFile = new File('output')
+
+        when:
+        generator.write(object, outputFile)
+
+        then:
+        1 * object.store(outputFile)
+        0 * _._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/PropertiesPersistableConfigurationObjectTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/PropertiesPersistableConfigurationObjectTest.groovy
new file mode 100644
index 0000000..53f129e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/PropertiesPersistableConfigurationObjectTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator
+
+import org.gradle.util.Matchers
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class PropertiesPersistableConfigurationObjectTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    String propertyValue
+    final PropertiesPersistableConfigurationObject object = new PropertiesPersistableConfigurationObject() {
+        @Override protected String getDefaultResourceName() {
+            return 'defaultResource.properties'
+        }
+
+        @Override protected void load(Properties properties) {
+            propertyValue = properties['prop']
+        }
+
+        @Override protected void store(Properties properties) {
+            properties['prop'] = propertyValue
+        }
+    }
+
+    def loadsFromPropertiesFile() {
+        def inputFile = tmpDir.file('input.properties')
+        inputFile.text = 'prop=value'
+
+        when:
+        object.load(inputFile)
+
+        then:
+        propertyValue == 'value'
+    }
+
+    def loadsFromDefaultResource() {
+        when:
+        object.loadDefaults()
+
+        then:
+        propertyValue == 'default-value'
+    }
+
+    def storesToXmlFile() {
+        object.loadDefaults()
+        propertyValue = 'modified-value'
+        def outputFile = tmpDir.file('output.properties')
+
+        when:
+        object.store(outputFile)
+
+        then:
+        Matchers.containsLine(outputFile.text, 'prop=modified-value')
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/XmlPersistableConfigurationObjectTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/XmlPersistableConfigurationObjectTest.groovy
new file mode 100644
index 0000000..65f2216
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/XmlPersistableConfigurationObjectTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.generator
+
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class XmlPersistableConfigurationObjectTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    String rootElement
+    final XmlPersistableConfigurationObject object = new XmlPersistableConfigurationObject(new XmlTransformer()) {
+        @Override protected String getDefaultResourceName() {
+            return 'defaultResource.xml'
+        }
+
+        @Override protected void load(Node xml) {
+            rootElement = xml.name() as String
+        }
+
+        @Override protected void store(Node xml) {
+            xml.name = rootElement
+        }
+    }
+
+    def loadsFromXmlFile() {
+        def inputFile = tmpDir.file('input.xml')
+        inputFile.text = '<some-xml/>'
+
+        when:
+        object.load(inputFile)
+
+        then:
+        rootElement == 'some-xml'
+    }
+
+    def loadsFromDefaultResource() {
+        when:
+        object.loadDefaults()
+
+        then:
+        rootElement == 'default-xml'
+    }
+
+    def storesToXmlFile() {
+        object.loadDefaults()
+        rootElement = 'modified-xml'
+        def outputFile = tmpDir.file('output.xml')
+
+        when:
+        object.store(outputFile)
+
+        then:
+        outputFile.text == '<modified-xml/>\n'
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/defaultResource.xml b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/defaultResource.xml
new file mode 100644
index 0000000..b562ead
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/generator/defaultResource.xml
@@ -0,0 +1 @@
+<default-xml/>
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
index 2825156..74f828d 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.api.tasks
 
 import org.gradle.BuildResult
 import org.gradle.GradleLauncher
-import org.gradle.GradleLauncherFactory
+import org.gradle.initialization.GradleLauncherFactory
 import org.gradle.api.internal.AbstractTask
 import org.junit.After
 import org.junit.Before
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/GradleLauncherMetaDataTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/GradleLauncherMetaDataTest.groovy
index 58ea1a9..b107bc5 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/GradleLauncherMetaDataTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/GradleLauncherMetaDataTest.groovy
@@ -21,11 +21,11 @@ import org.gradle.util.SetSystemProperties
 
 class GradleLauncherMetaDataTest extends Specification {
     @Rule public final SetSystemProperties sysProps = new SetSystemProperties()
-    private final GradleLauncherMetaData metaData = new GradleLauncherMetaData()
 
-    def usesSystemPropertyToDetermineLauncherName() {
+    def usesSystemPropertyToDetermineApplicationName() {
         System.setProperty("org.gradle.appname", "some-gradle-launcher")
-        def StringWriter writer = new StringWriter()
+        StringWriter writer = new StringWriter()
+        GradleLauncherMetaData metaData = new GradleLauncherMetaData()
 
         when:
         metaData.describeCommand(writer, "[options]", "<task-name>")
@@ -33,10 +33,11 @@ class GradleLauncherMetaDataTest extends Specification {
         then:
         writer.toString() == "some-gradle-launcher [options] <task-name>"
     }
-    
-    def usesDefaultValueWhenSystemPropertyNotSet() {
+
+    def usesDefaultApplicationNameWhenSystemPropertyNotSet() {
         System.clearProperty("org.gradle.appname")
-        def StringWriter writer = new StringWriter()
+        StringWriter writer = new StringWriter()
+        GradleLauncherMetaData metaData = new GradleLauncherMetaData()
 
         when:
         metaData.describeCommand(writer, "[options]", "<task-name>")
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
index 94593cd..1f79093 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
@@ -22,7 +22,7 @@ import static org.junit.Assert.*
 
 import org.gradle.BuildResult
 import org.gradle.GradleLauncher
-import org.gradle.GradleLauncherFactory
+
 import org.gradle.StartParameter
 import org.gradle.api.Project
 import org.gradle.api.file.FileCollection
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy
index 00ba21b..de4b733 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy
@@ -255,9 +255,14 @@ class CommandLineParserTest extends Specification {
         expect:
         def result = parser.parse(['a', '--option', 'b'])
         result.extraArguments == ['a', '--option', 'b']
+
+        parser.allowMixedSubcommandsAndOptions()
+
+        result = parser.parse(['a', '--option', 'b'])
+        result.extraArguments == ['a', '--option', 'b']
     }
-    
-    def canTreatOptionAsSubcommand() {
+
+    def canMapOptionToSubcommand() {
         parser.option('a').mapsToSubcommand('subcmd')
 
         expect:
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
index 176da87..f51c4b8 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -24,8 +24,8 @@ import org.gradle.api.logging.LogLevel;
 import org.gradle.groovy.scripts.UriScriptSource;
 import org.gradle.util.GUtil;
 import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
 import org.gradle.util.WrapUtil;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -34,18 +34,20 @@ import java.io.IOException;
 import java.util.*;
 
 import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultCommandLineConverterTest {
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+
+    private TestFile currentDir = testDir.file("current-dir");
     private File expectedBuildFile;
     private File expectedGradleUserHome = StartParameter.DEFAULT_GRADLE_USER_HOME;
-    private File expectedProjectDir;
+    private File expectedProjectDir = currentDir;
     private List<String> expectedTaskNames = toList();
     private Set<String> expectedExcludedTasks = toSet();
     private ProjectDependenciesBuildInstruction expectedProjectDependenciesBuildInstruction
@@ -63,15 +65,8 @@ public class DefaultCommandLineConverterTest {
     private StartParameter actualStartParameter;
     private boolean expectedProfile;
 
-    @Rule
-    public TemporaryFolder testDir = new TemporaryFolder();
     private final DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
 
-    @Before
-    public void setUp() throws IOException {
-        expectedProjectDir = new File("").getCanonicalFile();
-    }
-
     @Test
     public void withoutAnyOptions() {
         checkConversion();
@@ -103,7 +98,9 @@ public class DefaultCommandLineConverterTest {
     }
 
     private void checkConversion(final boolean embedded, String... args) {
-        actualStartParameter = commandLineConverter.convert(Arrays.asList(args));
+        actualStartParameter = new StartParameter();
+        actualStartParameter.setCurrentDir(currentDir);
+        commandLineConverter.convert(Arrays.asList(args), actualStartParameter);
         // We check the params passed to the build factory
         checkStartParameter(actualStartParameter);
         if (embedded) {
@@ -115,32 +112,56 @@ public class DefaultCommandLineConverterTest {
 
     @Test
     public void withSpecifiedGradleUserHomeDirectory() {
-        expectedGradleUserHome = testDir.getDir();
-        checkConversion("-g", expectedGradleUserHome.getAbsoluteFile().toString());
+        expectedGradleUserHome = testDir.file("home");
+        checkConversion("-g", expectedGradleUserHome.getAbsolutePath());
+
+        expectedGradleUserHome = currentDir.file("home");
+        checkConversion("-g", "home");
     }
 
     @Test
     public void withSpecifiedProjectDirectory() {
-        expectedProjectDir = testDir.getDir();
-        checkConversion("-p", expectedProjectDir.getAbsoluteFile().toString());
+        expectedProjectDir = testDir.file("project-dir");
+        checkConversion("-p", expectedProjectDir.getAbsolutePath());
+
+        expectedProjectDir = currentDir.file("project-dir");
+        checkConversion("-p", "project-dir");
     }
 
     @Test
     public void withSpecifiedBuildFileName() throws IOException {
-        expectedBuildFile = new File("somename").getCanonicalFile();
+        expectedBuildFile = testDir.file("somename");
+        expectedProjectDir = expectedBuildFile.getParentFile();
+        checkConversion("-b", expectedBuildFile.getAbsolutePath());
+
+        expectedBuildFile = currentDir.file("somename");
+        expectedProjectDir = expectedBuildFile.getParentFile();
         checkConversion("-b", "somename");
     }
 
     @Test
     public void withSpecifiedSettingsFileName() throws IOException {
+        File expectedSettingsFile = currentDir.file("somesettings");
+        expectedProjectDir = expectedSettingsFile.getParentFile();
+
         checkConversion("-c", "somesettings");
 
         assertThat(actualStartParameter.getSettingsScriptSource(), instanceOf(UriScriptSource.class));
-        assertThat(actualStartParameter.getSettingsScriptSource().getResource().getFile(), equalTo(new File(
-                "somesettings").getCanonicalFile()));
+        assertThat(actualStartParameter.getSettingsScriptSource().getResource().getFile(), equalTo(expectedSettingsFile));
     }
 
     @Test
+    public void withInitScripts() {
+        File script1 = currentDir.file("init1.gradle");
+        expectedInitScripts.add(script1);
+        checkConversion("-Iinit1.gradle");
+
+        File script2 = currentDir.file("init2.gradle");
+        expectedInitScripts.add(script2);
+        checkConversion("-Iinit1.gradle", "-Iinit2.gradle");
+    }
+    
+    @Test
     public void withSystemProperties() {
         final String prop1 = "gradle.prop1";
         final String valueProp1 = "value1";
@@ -326,17 +347,6 @@ public class DefaultCommandLineConverterTest {
     }
 
     @Test
-    public void withInitScripts() {
-        File script1 = new File("init1.gradle");
-        expectedInitScripts.add(script1);
-        checkConversion("-Iinit1.gradle");
-
-        File script2 = new File("init2.gradle");
-        expectedInitScripts.add(script2);
-        checkConversion("-Iinit1.gradle", "-Iinit2.gradle");
-    }
-
-    @Test
     public void withProfile() {
         expectedProfile = true;
         checkConversion("--profile");
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
new file mode 100644
index 0000000..5c5ad96
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.initialization
+
+import org.gradle.GradleLauncher
+import org.gradle.StartParameter
+import spock.lang.Specification
+
+class DefaultGradleLauncherFactoryTest extends Specification {
+    final CommandLineConverter<StartParameter> parameterConverter = Mock()
+    final DefaultGradleLauncherFactory factory = new DefaultGradleLauncherFactory();
+
+    def setup() {
+        factory.setCommandLineConverter(parameterConverter);
+    }
+
+    def cleanup() {
+        GradleLauncher.injectCustomFactory(null);
+    }
+
+    def registersSelfWithGradleLauncher() {
+        StartParameter startParameter = new StartParameter();
+
+        when:
+        def result = GradleLauncher.createStartParameter('a')
+
+        then:
+        result == startParameter
+        1 * parameterConverter.convert(['a']) >> startParameter
+    }
+
+    def newInstanceWithStartParameterAndRequestMetaData() {
+        StartParameter startParameter = new StartParameter();
+        BuildRequestMetaData metaData = new DefaultBuildRequestMetaData(System.currentTimeMillis());
+
+        expect:
+        def launcher = factory.newInstance(startParameter, metaData)
+        launcher.gradle.parent == null
+        launcher.gradle.startParameter == startParameter
+        launcher.gradle.services.get(BuildRequestMetaData) == metaData
+    }
+
+    def newInstanceWithStartParameterWhenNoBuildRunning() {
+        StartParameter startParameter = new StartParameter();
+
+        expect:
+        def launcher = factory.newInstance(startParameter)
+        launcher.gradle.parent == null
+        launcher.gradle.services.get(BuildRequestMetaData) instanceof DefaultBuildRequestMetaData
+    }
+
+    def newInstanceWithStartParameterWhenBuildRunning() {
+        StartParameter startParameter = new StartParameter();
+        BuildClientMetaData clientMetaData = Mock()
+        BuildRequestMetaData requestMetaData = new DefaultBuildRequestMetaData(clientMetaData, 90)
+        DefaultGradleLauncher parent = factory.newInstance(startParameter, requestMetaData);
+        parent.buildListener.buildStarted(parent.gradle)
+
+        expect:
+        def launcher = factory.newInstance(startParameter)
+        launcher.gradle.parent == parent.gradle
+        def request = launcher.gradle.services.get(BuildRequestMetaData)
+        request instanceof DefaultBuildRequestMetaData
+        request != requestMetaData
+        request.client == clientMetaData
+    }
+
+    def createStartParameter() {
+        StartParameter startParameter = new StartParameter();
+
+        when:
+        def result = factory.createStartParameter('a')
+
+        then:
+        result == startParameter
+        1 * parameterConverter.convert(['a']) >> startParameter
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.java
deleted file mode 100644
index a634303..0000000
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.initialization;
-
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.gradle.util.WrapUtil.toList;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultGradleLauncherFactoryTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-    private final CommandLineConverter<StartParameter> parameterConverter = context.mock(CommandLineConverter.class);
-    private final DefaultGradleLauncherFactory factory = new DefaultGradleLauncherFactory();
-
-    @Before
-    public void setUp() {
-        factory.setCommandLineConverter(parameterConverter);
-    }
-
-    @After
-    public void tearDown() {
-        GradleLauncher.injectCustomFactory(null);
-    }
-
-    @Test
-    public void registersSelfWithGradleLauncher() {
-        final StartParameter startParameter = new StartParameter();
-        context.checking(new Expectations() {{
-            allowing(parameterConverter).convert(toList("a"));
-            will(returnValue(startParameter));
-        }});
-
-        assertThat(GradleLauncher.createStartParameter("a"), sameInstance(startParameter));
-    }
-    
-    @Test
-    public void newInstanceWithStartParameter() {
-        final StartParameter startParameter = new StartParameter();
-        assertNotNull(factory.newInstance(startParameter));
-    }
-
-    @Test
-    public void newInstanceWithCommandLineArgs() {
-        final StartParameter startParameter = new StartParameter();
-        context.checking(new Expectations() {{
-            allowing(parameterConverter).convert(toList("A", "B"));
-            will(returnValue(startParameter));
-        }});
-        assertNotNull(factory.newInstance("A", "B"));
-    }
-
-    @Test
-    public void createStartParameter() {
-        final StartParameter startParameter = new StartParameter();
-        context.checking(new Expectations() {{
-            allowing(parameterConverter).convert(toList("A", "B"));
-            will(returnValue(startParameter));
-        }});
-
-        assertThat(factory.createStartParameter("A", "B"), sameInstance(startParameter));
-    }
-}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy
index af2b834..4860c9a 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy
@@ -15,15 +15,14 @@
  */
 package org.gradle.initialization
 
-
-import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.BuildResult
+import org.gradle.api.internal.GradleInternal
+import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.gradle.api.invocation.Gradle
-import org.gradle.BuildResult
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
 
 @RunWith(JMock.class)
 class NestedBuildTrackerTest {
@@ -37,8 +36,8 @@ class NestedBuildTrackerTest {
 
     @Test
     public void setsCurrentBuildWhenBuildStartsAndStops() {
-        def build = context.mock(Gradle.class, 'build1')
-        def build2 = context.mock(Gradle.class, 'build2')
+        def build = context.mock(GradleInternal.class, 'build1')
+        def build2 = context.mock(GradleInternal.class, 'build2')
 
         tracker.buildStarted(build)
         assertThat(tracker.currentBuild, sameInstance(build))
@@ -55,8 +54,8 @@ class NestedBuildTrackerTest {
 
     @Test
     public void pushesBuildWhenBuildStartsWhileOneIsCurrentlyRunning() {
-        def build = context.mock(Gradle.class, 'build1')
-        def build2 = context.mock(Gradle.class, 'build2')
+        def build = context.mock(GradleInternal.class, 'build1')
+        def build2 = context.mock(GradleInternal.class, 'build2')
 
         tracker.buildStarted(build)
         assertThat(tracker.currentBuild, sameInstance(build))
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
index 5ff2c10..1a24529 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
@@ -17,6 +17,7 @@
 package org.gradle.invocation;
 
 import groovy.lang.Closure;
+import org.gradle.BuildListener;
 import org.gradle.StartParameter;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.initialization.dsl.ScriptHandler;
@@ -157,6 +158,56 @@ public class DefaultGradleTest {
     }
 
     @Test
+    public void broadcastsBuildStartedEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(BuildListener.class, "buildStarted", closure);
+        }});
+
+        gradle.buildStarted(closure);
+    }
+
+    @Test
+    public void broadcastsSettingsEvaluatedEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(BuildListener.class, "settingsEvaluated", closure);
+        }});
+
+        gradle.settingsEvaluated(closure);
+    }
+
+    @Test
+    public void broadcastsProjectsLoadedEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(BuildListener.class, "projectsLoaded", closure);
+        }});
+
+        gradle.projectsLoaded(closure);
+    }
+
+    @Test
+    public void broadcastsProjectsEvaluatedEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(BuildListener.class, "projectsEvaluated", closure);
+        }});
+
+        gradle.projectsEvaluated(closure);
+    }
+
+    @Test
+    public void broadcastsBuildFinishedEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(BuildListener.class, "buildFinished", closure);
+        }});
+
+        gradle.buildFinished(closure);
+    }
+
+    @Test
     public void usesSpecifiedLogger() {
         final Object logger = new Object();
         context.checking(new Expectations() {{
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
new file mode 100644
index 0000000..3242a1d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/ActionBroadcastTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.listener
+
+import spock.lang.Specification
+import org.gradle.api.Action
+
+class ActionBroadcastTest extends Specification {
+    final ActionBroadcast<String> broadcast = new ActionBroadcast<String>()
+
+    def broadcastsEventsToAction() {
+        Action<String> action = Mock()
+        broadcast.add(action)
+
+        when:
+        broadcast.execute('value')
+
+        then:
+        1 * action.execute('value')
+        0 * action._
+    }
+
+    def broadcastsEventsToClosure() {
+        Closure action = Mock()
+        broadcast.add(action)
+
+        when:
+        broadcast.execute('value')
+
+        then:
+        _ * action.maximumNumberOfParameters >> 1
+        1 * action.call('value')
+        0 * action._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
index f98d492..ad7843d 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
 @RunWith(JMock.class)
 public class ChannelMessageMarshallingDispatchTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Dispatch<Message> delegate = context.mock(Dispatch.class);
+    private final Dispatch<Object> delegate = context.mock(Dispatch.class);
     private final ChannelMessageMarshallingDispatch dispatch = new ChannelMessageMarshallingDispatch(delegate);
 
     @Test
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
index 6b6d331..7646be8 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
 @RunWith(JMock.class)
 public class ChannelMessageUnmarshallingDispatchTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Dispatch<Message> delegate = context.mock(Dispatch.class);
+    private final Dispatch<Object> delegate = context.mock(Dispatch.class);
     private final ChannelMessageUnmarshallingDispatch dispatch = new ChannelMessageUnmarshallingDispatch(delegate);
 
     @Test
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
index f62c0e1..fcd6ca8 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
@@ -31,9 +31,10 @@ import java.net.URI;
 import java.util.HashMap;
 import java.util.Map;
 
-import static org.gradle.util.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.gradle.util.Matchers.strictlyEqual;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 @RunWith(JMock.class)
 public class DefaultObjectConnectionTest {
@@ -161,15 +162,15 @@ public class DefaultObjectConnectionTest {
     }
 
     private class TestConnection {
-        Map<Object, Dispatch<Message>> channels = new HashMap<Object, Dispatch<Message>>();
+        Map<Object, Dispatch<Object>> channels = new HashMap<Object, Dispatch<Object>>();
 
-        MultiChannelConnection<Message> getSender() {
-            return new MultiChannelConnection<Message>() {
-                public Dispatch<Message> addOutgoingChannel(Object channelKey) {
+        MultiChannelConnection<Object> getSender() {
+            return new MultiChannelConnection<Object>() {
+                public Dispatch<Object> addOutgoingChannel(Object channelKey) {
                     return channels.get(channelKey);
                 }
 
-                public void addIncomingChannel(Object channelKey, Dispatch<Message> dispatch) {
+                public void addIncomingChannel(Object channelKey, Dispatch<Object> dispatch) {
                     throw new UnsupportedOperationException();
                 }
 
@@ -191,13 +192,13 @@ public class DefaultObjectConnectionTest {
             };
         }
 
-        MultiChannelConnection<Message> getReceiver() {
-            return new MultiChannelConnection<Message>() {
-                public Dispatch<Message> addOutgoingChannel(Object channelKey) {
+        MultiChannelConnection<Object> getReceiver() {
+            return new MultiChannelConnection<Object>() {
+                public Dispatch<Object> addOutgoingChannel(Object channelKey) {
                     throw new UnsupportedOperationException();
                 }
 
-                public void addIncomingChannel(Object channelKey, Dispatch<Message> dispatch) {
+                public void addIncomingChannel(Object channelKey, Dispatch<Object> dispatch) {
                     channels.put(channelKey, dispatch);
                 }
 
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
index 529b5a5..485ea59 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
@@ -162,7 +162,7 @@ class MessageTest extends Specification {
 
     private Object transport(Object arg) {
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        new TestPayloadMessage(payload: arg).send(outputStream);
+        Message.send(new TestPayloadMessage(payload: arg), outputStream);
 
         ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
         def message = Message.receive(inputStream, dest);
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
index c545ef7..2c32462 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
@@ -13,9 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.messaging.remote.internal
 
 import org.gradle.api.Action
@@ -38,4 +35,15 @@ class TcpConnectorTest extends MultithreadedTestCase {
 
         incomingConnector.requestStop()
     }
+
+    @Test
+    public void outgoingConnectorThrowsConnectExceptionWhenCannotConnect() {
+        TcpOutgoingConnector outgoingConnector = new TcpOutgoingConnector(getClass().classLoader)
+
+        try {
+            outgoingConnector.connect(new URI("tcp://localhost:12345"))
+            fail()
+        } catch (ConnectException e) {
+        }
+    }
 }
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy
index a72201a..d1eea95 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy
@@ -30,8 +30,8 @@ import org.codehaus.groovy.control.CompilerConfiguration
 import org.gradle.BuildResult
 import org.gradle.api.Task
 import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.internal.AsmBackedClassGenerator
 import org.gradle.api.internal.ClassGenerator
-import org.gradle.api.internal.GroovySourceGenerationBackedClassGenerator
 import org.gradle.api.internal.artifacts.configurations.DefaultConfiguration
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
@@ -55,7 +55,7 @@ class HelperUtil {
 
     public static final Closure TEST_CLOSURE = {}
     public static final Spec TEST_SEPC  = new AndSpec()
-    private static final ClassGenerator CLASS_GENERATOR = new GroovySourceGenerationBackedClassGenerator()
+    private static final ClassGenerator CLASS_GENERATOR = new AsmBackedClassGenerator()
     private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(CLASS_GENERATOR))
 
     static <T extends Task> T createTask(Class<T> type) {
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/Matchers.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/Matchers.java
index fb1ee1d..c8a78fc 100644
--- a/subprojects/gradle-core/src/test/groovy/org/gradle/util/Matchers.java
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/Matchers.java
@@ -127,18 +127,7 @@ public class Matchers {
         return new BaseMatcher<String>() {
             public boolean matches(Object o) {
                 String str = (String) o;
-                BufferedReader reader = new BufferedReader(new StringReader(str));
-                String line;
-                try {
-                    while ((line = reader.readLine()) != null) {
-                        if (matcher.matches(line)) {
-                            return true;
-                        }
-                    }
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-                return false;
+                return containsLine(str, matcher);
             }
 
             public void describeTo(Description description) {
@@ -147,6 +136,25 @@ public class Matchers {
         };
     }
 
+    public static boolean containsLine(String action, String expected) {
+        return containsLine(action, equalTo(expected));
+    }
+    
+    public static boolean containsLine(String actual, Matcher<? super String> matcher) {
+        BufferedReader reader = new BufferedReader(new StringReader(actual));
+        String line;
+        try {
+            while ((line = reader.readLine()) != null) {
+                if (matcher.matches(line)) {
+                    return true;
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return false;
+    }
+
     @Factory
     public static Matcher<Iterable<?>> isEmpty() {
         return new BaseMatcher<Iterable<?>>() {
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/internal/tasks/generator/defaultResource.properties b/subprojects/gradle-core/src/test/resources/org/gradle/api/internal/tasks/generator/defaultResource.properties
new file mode 100644
index 0000000..2a90d9d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/internal/tasks/generator/defaultResource.properties
@@ -0,0 +1 @@
+prop=default-value
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/internal/tasks/generator/defaultResource.xml b/subprojects/gradle-core/src/test/resources/org/gradle/api/internal/tasks/generator/defaultResource.xml
new file mode 100644
index 0000000..b562ead
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/internal/tasks/generator/defaultResource.xml
@@ -0,0 +1 @@
+<default-xml/>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/docs.gradle b/subprojects/gradle-docs/docs.gradle
index d8a182a..343b7f6 100644
--- a/subprojects/gradle-docs/docs.gradle
+++ b/subprojects/gradle-docs/docs.gradle
@@ -142,18 +142,14 @@ task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook])
 task dslHtml(type: Docbook2Xhtml, dependsOn: dslStandaloneDocbook) {
     source dslStandaloneDocbook.destFile
     destDir = new File(docsDir, 'dsl')
-    stylesheetName = 'userGuideHtml.xsl'
+    stylesheetName = 'dslHtml.xsl'
     resources = fileTree {
-        from userguideSrcDir
-        include 'img/*.png'
-    }
-    resources += fileTree {
         from cssSrcDir
         include '*.css'
     }
 }
 
-task userguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook, dslDocbook]) {
+task userguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
     inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
     inputs.files fileTree(dir: docbookSrc, includes: ['*.xml'])
     sourceFile = new File(userguideSrcDir, 'userguide.xml')
@@ -279,7 +275,7 @@ task distDocs(type: Docbook2Xhtml, dependsOn: userguideFragmentSrc) {
     stylesheetName = 'standaloneHtml.xsl'
 }
 
-task websiteUserguideSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples, samplesDocbook, dslDocbook]) {
+task websiteUserguideSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples, samplesDocbook]) {
     inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
     inputs.files fileTree(dir: docbookSrc, includes: ['*.xml'])
     sourceFile = new File(userguideSrcDir, 'userguide.xml')
@@ -329,7 +325,7 @@ task userguide {
 }
 
 task docs {
-    dependsOn javadoc, groovydoc, userguide, distDocs, samplesDocs
+    dependsOn javadoc, groovydoc, userguide, distDocs, samplesDocs, dslHtml
     description = 'Generates all documentation'
     group = 'documentation'
 }
diff --git a/subprojects/gradle-docs/src/docs/css/base.css b/subprojects/gradle-docs/src/docs/css/base.css
index 0de2bd2..5fc5178 100644
--- a/subprojects/gradle-docs/src/docs/css/base.css
+++ b/subprojects/gradle-docs/src/docs/css/base.css
@@ -1,18 +1,3 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
 
 /*
  * Basic layout
@@ -234,6 +219,13 @@ pre {
    margin-top: 0;
 }
 
+/**
+ * Inline content
+ */
+.literal, .userinput, .filename {
+    white-space: nowrap;
+}
+
 /*
  * Code highlighting
  */
diff --git a/subprojects/gradle-docs/src/docs/css/dsl.css b/subprojects/gradle-docs/src/docs/css/dsl.css
new file mode 100644
index 0000000..47aeb7f
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/css/dsl.css
@@ -0,0 +1,36 @@
+
+.sidebar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 7em;
+    overflow: auto;
+    padding-top: 2em;
+    padding-left: 2em;
+    padding-right: 2em;
+}
+
+.sidebar ul {
+    padding-left: 0;
+    padding-right: 0;
+}
+
+.sidebar li {
+    list-style: none;
+}
+
+.sidebar li.sidebarHeading {
+    margin-top: 1.4em;
+    color: #a2a2a2;
+}
+
+.content {
+    position: absolute;
+    top: 0;
+    left: 13em;
+}
+
+.book .titlepage .releaseinfo {
+    color: #666666;
+}
diff --git a/subprojects/gradle-docs/src/docs/css/print.css b/subprojects/gradle-docs/src/docs/css/print.css
index 1854108..19d7bbc 100644
--- a/subprojects/gradle-docs/src/docs/css/print.css
+++ b/subprojects/gradle-docs/src/docs/css/print.css
@@ -1,18 +1,4 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+
 @page {
     size: A4;
     padding: 0;
diff --git a/subprojects/gradle-docs/src/docs/css/style.css b/subprojects/gradle-docs/src/docs/css/style.css
index 455e51c..85c76e7 100644
--- a/subprojects/gradle-docs/src/docs/css/style.css
+++ b/subprojects/gradle-docs/src/docs/css/style.css
@@ -1,19 +1,4 @@
 
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
 body {
     margin-top: 2em;
     margin-bottom: 2em;
@@ -63,119 +48,3 @@ h3 {
     font-size: 130%;
     margin-top: 1.4em;
 }
-
-/*
- * Table of Contents
- */
-
-.toc dl, .list-of-examples dl {
-    margin-top: 0;
-    margin-bottom: 0;
-}
-
-.book .toc > dl > dt {
-    margin-top: 0.6em;
-}
-
-.book .toc, .book .list-of-examples {
-    margin-top: 2em;
-}
-
-/*
- * HEADER/FOOTER
- */
-
-.navheader {
-    margin-bottom: 3em;
-    padding-bottom: 1em;
-    width: 100%;
-    border-bottom: solid #d0d0d0 0.4em;
-}
-
-.navfooter {
-    margin-top: 3em;
-    padding-top: 1em;
-    width: 100%;
-    border-top: solid #d0d0d0 0.4em;
-}
-
-.navbar {
-    color: #a2a2a2;
-    font-size: 90%;
-    width: 100%;
-    text-align: right;
-}
-
-.navbar a {
-    color: #444444;
-}
-
-.navbar span {
-    padding-left: .8em;
-    padding-right: .8em;
-}
-
-/**
- * TITLEPAGE
- */
-
-.book > .titlepage {
-    margin-top: 2.2em;
-    margin-bottom: 2em;
-}
-
-.book .titlepage div.title {
-    border: solid #d0d0d0 1px;
-    background-color: #f5f5f5;
-    margin-left: -1.8em;
-    margin-right: -1.8em;
-    padding-left: 1.8em;
-    padding-right: 1.8em;
-    margin-bottom: 2.2em;
-}
-
-.titlepage h1.title {
-    font-size: 280%;
-    margin-bottom: 0;
-    color: #4a9935;
-}
-
-.titlepage h2.subtitle {
-    font-size: 160%;
-    font-style: italic;
-    margin-top: 0;
-    margin-bottom: 0;
-    color: #4a9935;
-}
-
-.titlepage .releaseinfo {
-    color: #666666;
-    font-size: 120%;
-    margin-top: 2em;
-    margin-bottom: 2.6em;
-}
-
-.titlepage .author {
-    color: #666666;
-}
-
-.titlepage .copyright {
-    color: #a2a2a2;
-    font-size: 90%;
-}
-
-.titlepage .legalnotice {
-    color: #a2a2a2;
-    font-size: 90%;
-}
-
-.literal, .userinput, .filename {
-    white-space: nowrap;
-}
-
-/*
- * Single page html
- */
-.book .chapter {
-    margin-top: 4em;
-}
diff --git a/subprojects/gradle-docs/src/docs/css/style.css b/subprojects/gradle-docs/src/docs/css/userguide.css
similarity index 53%
copy from subprojects/gradle-docs/src/docs/css/style.css
copy to subprojects/gradle-docs/src/docs/css/userguide.css
index 455e51c..263547f 100644
--- a/subprojects/gradle-docs/src/docs/css/style.css
+++ b/subprojects/gradle-docs/src/docs/css/userguide.css
@@ -1,69 +1,3 @@
-
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-body {
-    margin-top: 2em;
-    margin-bottom: 2em;
-    margin-left: 4em;
-    margin-right: 4em;
-}
-
-a {
-    text-decoration: none;
-    border-bottom: dotted 1px;
-    /*outline: none;*/
-}
-
-p {
-   /* ensure paragraphs remain close to their parent heading */
-   margin-top: 0;
-   margin-bottom: 1em;
-}
-
-h1, h2, h3, h4, h5 {
-    /* ensure paragraphs remain close to their parent heading */
-    margin-bottom: 0.2em;
-    color: #4a9935;
-}
-
-h1 {
-    font-size: 180%;
-}
-
-h2 {
-    font-size: 180%;
-}
-
-h3 {
-    font-size: 110%;
-}
-
-.chapter h1 {
-    margin-bottom: 1.0em;
-}
-
-.appendix h1 {
-    margin-bottom: 1.0em;
-}
-
-.section h2 {
-    font-size: 130%;
-    margin-top: 1.4em;
-}
-
 /*
  * Table of Contents
  */
@@ -169,10 +103,6 @@ h3 {
     font-size: 90%;
 }
 
-.literal, .userinput, .filename {
-    white-space: nowrap;
-}
-
 /*
  * Single page html
  */
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/dslHtml.xsl b/subprojects/gradle-docs/src/docs/stylesheets/dslHtml.xsl
new file mode 100644
index 0000000..f4c3ef1
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/dslHtml.xsl
@@ -0,0 +1,97 @@
+<!--
+  ~ Copyright 2010 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<xsl:stylesheet
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+    <xsl:import href="html/chunkfast.xsl"/>
+    <xsl:import href="userGuideHtmlCommon.xsl"/>
+
+    <xsl:param name="root.filename">index</xsl:param>
+    <xsl:param name="chunk.section.depth">0</xsl:param>
+    <xsl:param name="chunk.quietly">1</xsl:param>
+    <xsl:param name="use.id.as.filename">1</xsl:param>
+    <xsl:param name="section.autolabel">0</xsl:param>
+    <xsl:param name="chapter.autolabel">0</xsl:param>
+    <xsl:param name="appendix.autolabel">0</xsl:param>
+
+    <!-- No table of contents -->
+    <xsl:param name="generate.toc"/>
+
+    <!-- customise the stylesheets to add to the <head> element -->
+    <xsl:template name="output.html.stylesheets">
+        <link href="base.css" rel="stylesheet" type="text/css"/>
+        <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="dsl.css" rel="stylesheet" type="text/css"/>
+    </xsl:template>
+
+    <!-- customise the layout of the html page -->
+    <xsl:template name="chunk-element-content">
+        <xsl:param name="prev"/>
+        <xsl:param name="next"/>
+        <xsl:param name="nav.context"/>
+        <xsl:param name="content">
+            <xsl:apply-imports/>
+        </xsl:param>
+
+        <html>
+            <xsl:call-template name="html.head">
+                <xsl:with-param name="prev" select="$prev"/>
+                <xsl:with-param name="next" select="$next"/>
+            </xsl:call-template>
+
+            <body>
+                <xsl:call-template name="body.attributes"/>
+                <div class="sidebar">
+                    <ul>
+                        <xsl:apply-templates select="/" mode="sidebar"/>
+                    </ul>
+                </div>
+                <div class="content">
+                    <xsl:copy-of select="$content"/>
+                </div>
+            </body>
+        </html>
+        <xsl:value-of select="$chunk.append"/>
+    </xsl:template>
+
+    <!--
+      - Navigation sidebar
+      -->
+
+    <xsl:template match="book" mode="sidebar">
+        <li>
+            <xsl:call-template name="customXref">
+                <xsl:with-param name="target" select="."/>
+                <xsl:with-param name="content">
+                    <xsl:text>Contents</xsl:text>
+                </xsl:with-param>
+            </xsl:call-template>
+        </li>
+        <xsl:apply-templates select="section[1]/table[@role = 'dslTypes']" mode="sidebar"/>
+    </xsl:template>
+
+    <xsl:template match="table" mode="sidebar">
+        <li class='sidebarHeading'>
+            <xsl:value-of select="title"/>
+        </li>
+        <xsl:apply-templates select="tr/td[1]" mode="sidebar"/>
+    </xsl:template>
+
+    <xsl:template match="td" mode="sidebar">
+        <li>
+            <xsl:apply-templates select="link"/>
+        </li>
+    </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl b/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl
index 1baf7ee..8c07186 100644
--- a/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl
+++ b/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl
@@ -24,6 +24,8 @@
     <xsl:param name="chapter.autolabel">0</xsl:param>
     <xsl:param name="appendix.autolabel">0</xsl:param>
 
+    <!-- Inline the stylesheets directly into the generated html -->
+
     <xsl:template name="output.html.stylesheets">
     </xsl:template>
 
@@ -31,6 +33,7 @@
         <style type="text/css">
             <xi:include href="base.css" parse="text"/>
             <xi:include href="style.css" parse="text"/>
+            <xi:include href="userguide.css" parse="text"/>
         </style>
     </xsl:template>
 </xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl b/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
index d341900..39f9b83 100644
--- a/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
+++ b/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
@@ -31,6 +31,7 @@
     <xsl:template name="output.html.stylesheets">
         <link href="base.css" rel="stylesheet" type="text/css"/>
         <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="userguide.css" rel="stylesheet" type="text/css"/>
     </xsl:template>
 
     <xsl:param name="generate.toc">
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl b/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl
index 73c8578..3d542cc 100644
--- a/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl
+++ b/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl
@@ -22,6 +22,7 @@
     <xsl:template name="output.html.stylesheets">
         <link href="base.css" rel="stylesheet" type="text/css"/>
         <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="userguide.css" rel="stylesheet" type="text/css"/>
         <link href="print.css" rel="stylesheet" type="text/css" media="print"/>
     </xsl:template>
 
diff --git a/subprojects/gradle-docs/src/docs/userguide/commandLine.xml b/subprojects/gradle-docs/src/docs/userguide/commandLine.xml
index 0491c7e..a476d16 100644
--- a/subprojects/gradle-docs/src/docs/userguide/commandLine.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/commandLine.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<appendix id='gradle_command_line' xmlns:xi="http://www.w3.org/2001/XInclude">
+<appendix id='gradle_command_line'>
     <title>Gradle Command Line</title>
     <para>The <command>gradle</command> command has the following usage:
         <cmdsynopsis>
@@ -144,24 +144,93 @@
             </para></listitem>
         </varlistentry>
     </variablelist>
+    <para>The above information is printed to the console when you execute <userinput>gradle -h</userinput>.</para>
 
-    <para>The following options are deprecated and will be removed in a future version of Gradle:</para>
-    <variablelist>
-        <varlistentry>
-            <term><option>-n</option>, <option>--dependencies</option></term>
-            <listitem><para>(deprecated) Show list of all project dependencies. See <xref linkend="sec:listing_dependencies"/>.
-            </para></listitem>
-        </varlistentry>
+    <section>
+        <title>Deprecated command-line options</title>
+        <para>The following options are deprecated and will be removed in a future version of Gradle:</para>
+        <variablelist>
+            <varlistentry>
+                <term><option>-n</option>, <option>--dependencies</option></term>
+                <listitem><para>(deprecated) Show list of all project dependencies. You should use <userinput>gradle dependencies</userinput>
+                    instead. See <xref linkend="sec:listing_dependencies"/>.
+                </para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><option>-r</option>, <option>--properties</option></term>
+                <listitem><para>(deprecated) Show list of all available project properties. You should use <userinput>gradle properties</userinput>
+                    instead. See <xref linkend="sec:listing_properties"/>.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><option>-t</option>, <option>--tasks</option></term>
+                <listitem><para>(deprecated) Show list of available tasks. You should use <userinput>gradle tasks</userinput>
+                    instead. See <xref linkend="sec:listing_tasks"/>.</para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </section>
+
+    <section>
+        <title>Experimental command-line options</title>
+        <para>The following options are experimental:</para>
+        <variablelist>
+            <varlistentry>
+                <term><option>--daemon</option></term>
+                <listitem><para>Uses the Gradle daemon to run the build. Starts the daemon if not running.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><option>--foreground</option></term>
+                <listitem><para>Starts the Gradle daemon in the foreground.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><option>--no-daemon</option></term>
+                <listitem><para>Do not use the Gradle daemon to run the build.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><option>--stop</option></term>
+                <listitem><para>Stops the Gradle daemon if it is running.</para></listitem>
+            </varlistentry>
+        </variablelist>
+    </section>
+
+    <section>
+        <title>System properties</title>
+        <para>The following system properties are available for the <command>gradle</command> command. Note that
+            command-line options take precedence over system properties.
+        </para>
         <varlistentry>
-            <term><option>-r</option>, <option>--properties</option></term>
-            <listitem><para>(deprecated) Show list of all available project properties. See <xref linkend="sec:listing_properties"/>.</para></listitem>
+            <term><literal>gradle.user.home</literal></term>
+            <listitem><para>Specifies the Gradle user home directory.</para> </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-t</option>, <option>--tasks</option></term>
-            <listitem><para>(deprecated) Show list of available tasks. See <xref linkend="sec:listing_tasks"/>.
-            </para></listitem>
+            <term><literal>org.gradle.daemon</literal></term>
+            <listitem><para>When set to <literal>true</literal>, use the Gradle daemon to run the build.</para>
+            </listitem>
         </varlistentry>
-    </variablelist>
+    </section>
 
-    <para>The same information is printed to the console when you execute <userinput>gradle -h</userinput>.</para>
+    <section>
+        <title>Environment variables</title>
+        <para>The following environment variables are available for the <command>gradle</command> command. Note that
+            command-line options and system properties take precedence over environment variables.
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term><literal>GRADLE_HOME</literal></term>
+                <listitem><para>Specifies the root directory of the Gradle installation to use.</para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><literal>GRADLE_OPTS</literal></term>
+                <listitem><para>Specifies command-line arguments to use to start the JVM. This can be useful for setting
+                    the system properties to use for running Gradle. For example you could set <literal>GRADLE_OPTS="-Dorg.gradle.daemon=true"</literal>
+                    to use the Gradle daemon without needing to use the <option>--daemon</option> option every time you
+                    run Gradle.
+                </para></listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><literal>GRADLE_USER_HOME</literal></term>
+                <listitem><para>Specifies the Gradle user home directory.</para></listitem>
+            </varlistentry>
+        </variablelist>
+    </section>
 </appendix>
diff --git a/subprojects/gradle-docs/src/docs/userguide/dsl/dsl.xml b/subprojects/gradle-docs/src/docs/userguide/dsl/dsl.xml
index f422088..934a7b5 100644
--- a/subprojects/gradle-docs/src/docs/userguide/dsl/dsl.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/dsl/dsl.xml
@@ -1,36 +1,42 @@
-<appendix id="dsl">
-    <title>Build script reference</title>
-    <note>
-        <para>This chapter is under construction.</para>
-    </note>
-    <para>This chapter describes the various types which make up the Gradle build DSL.</para>
+<book id="dsl">
+    <bookinfo>
+        <title>Gradle DSL Reference</title>
+    </bookinfo>
 
-    <!--
-    -
-    - To add more types to this chapter, add them to one of the following tables. The contents of
-    - ${classname}.xml for each type listed below is added to this chapter.
-    -
-    -->
+    <section>
+        <title>Introduction</title>
+        <note>
+            <para>This reference guide is under construction.</para>
+        </note>
+        <para>This reference guide describes the various types which make up the Gradle build DSL.</para>
 
-    <table>
-        <title>Core types</title>
-        <tr>
-            <td>org.gradle.api.Project</td>
-        </tr>
-        <tr>
-            <td>org.gradle.api.tasks.SourceSet</td>
-        </tr>
-    </table>
-    <table>
-        <title>Task types</title>
-        <tr>
-            <td>org.gradle.plugins.idea.IdeaProject</td>
-        </tr>
-        <tr>
-            <td>org.gradle.api.tasks.bundling.Tar</td>
-        </tr>
-        <tr>
-            <td>org.gradle.api.tasks.bundling.Zip</td>
-        </tr>
-    </table>
-</appendix>
\ No newline at end of file
+        <!--
+        -
+        - To add more types to this guide, add them to one of the following tables. The contents of
+        - ${classname}.xml for each type listed below is then included in the guide.
+        -
+        -->
+
+        <table>
+            <title>Core types</title>
+            <tr>
+                <td>org.gradle.api.Project</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.tasks.SourceSet</td>
+            </tr>
+        </table>
+        <table>
+            <title>Task types</title>
+            <tr>
+                <td>org.gradle.plugins.idea.IdeaProject</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.tasks.bundling.Tar</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.tasks.bundling.Zip</td>
+            </tr>
+        </table>
+    </section>
+</book>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml b/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.api.tasks.GeneratorTask.xml
similarity index 56%
copy from subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
copy to subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.api.tasks.GeneratorTask.xml
index 421e73f..0612893 100644
--- a/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.api.tasks.GeneratorTask.xml
@@ -1,5 +1,4 @@
 <section>
-    <para>Generates an IDEA project file.</para>
     <section>
         <title>Properties</title>
         <table>
@@ -12,11 +11,7 @@
             </thead>
             <tr>
                 <td>outputFile</td>
-                <td>The ipr file.</td>
-            </tr>
-            <tr>
-                <td>javaVersion</td>
-                <td>The java version used for defining the project sdk.</td>
+                <td>The output file.</td>
             </tr>
         </table>
     </section>
@@ -30,11 +25,6 @@
                     <td>Description</td>
                 </tr>
             </thead>
-            <tr>
-                <td><literal>withXml</literal></td>
-                <td><literal>IdeaProject withXml(Closure closure)</literal></td>
-                <td>Adds a closure to be called when the ipr xml has been created.</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml b/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.api.tasks.XmlGeneratorTask.xml
similarity index 72%
copy from subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
copy to subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.api.tasks.XmlGeneratorTask.xml
index 421e73f..dc9d1ee 100644
--- a/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.api.tasks.XmlGeneratorTask.xml
@@ -1,5 +1,4 @@
 <section>
-    <para>Generates an IDEA project file.</para>
     <section>
         <title>Properties</title>
         <table>
@@ -10,14 +9,6 @@
                     <td>Description</td>
                 </tr>
             </thead>
-            <tr>
-                <td>outputFile</td>
-                <td>The ipr file.</td>
-            </tr>
-            <tr>
-                <td>javaVersion</td>
-                <td>The java version used for defining the project sdk.</td>
-            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml b/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
index 421e73f..9e2d4fe 100644
--- a/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/dsl/org.gradle.plugins.idea.IdeaProject.xml
@@ -11,12 +11,8 @@
                 </tr>
             </thead>
             <tr>
-                <td>outputFile</td>
-                <td>The ipr file.</td>
-            </tr>
-            <tr>
                 <td>javaVersion</td>
-                <td>The java version used for defining the project sdk.</td>
+                <td>The java version used for defining the project SDK.</td>
             </tr>
         </table>
     </section>
@@ -30,11 +26,6 @@
                     <td>Description</td>
                 </tr>
             </thead>
-            <tr>
-                <td><literal>withXml</literal></td>
-                <td><literal>IdeaProject withXml(Closure closure)</literal></td>
-                <td>Adds a closure to be called when the ipr xml has been created.</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml b/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml
index 98bac4e..f95cde0 100644
--- a/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml
@@ -21,21 +21,40 @@
         making it possible to import the project into Eclipse (<guimenuitem>File</guimenuitem> - <guimenuitem>Import...</guimenuitem> - <guimenuitem>Existing Projects into Workspace</guimenuitem>).
         Both external and project dependencies are considered.</para>
 
-    <para>The Eclipse plugin will create different files depending on the other plugins used. If used together with
-        the <link linkend="java_plugin">Java plugin</link>, <filename>.project</filename> and <filename>.classpath</filename>
-        files will be generated. If used with the <link linkend="war_plugin">War plugin</link>, additional wtp files
-        will be generated.</para>
+    <para>The Eclipse plugin will create different files depending on which other plugins used:</para>
+    <table>
+        <title>Eclipse support for other plugins</title>
+        <thead>
+            <tr><td>Plugin</td><td>Description</td></tr>
+        </thead>
+        <tr>
+            <td>None</td><td>Generates minimal <filename>.project</filename> file.</td>
+        </tr>
+        <tr>
+            <td><link linkend="java_plugin">Java</link></td><td>Adds Java configuration to <filename>.project</filename>.
+            Generates <filename>.classpath</filename> and JDT settings file.</td>
+        </tr>
+        <tr>
+            <td><link linkend="groovy_plugin">Groovy</link></td><td>Adds Groovy configuration to <filename>.project</filename> file.</td>
+        </tr>
+        <tr>
+            <td><link linkend="scala_plugin">Scala</link></td><td>Adds Scala support to <filename>.project</filename> file.</td>
+        </tr>
+        <tr>
+            <td><link linkend="war_plugin">War</link></td><td>Adds web application support to <filename>.project</filename>.
+            Generates WTP settings files.</td>
+        </tr>
+    </table>
+
 <section>
         <title>Usage</title>
     <para>To use the Eclipse plugin, include in your build script:</para>
     <sample id="useEclipsePlugin" dir="eclipse" title="Using the Eclipse plugin">
         <sourcefile file="build.gradle" snippet="use-plugin"/>
     </sample>
-    <para>There are several tasks (presented in <xref linkend='eclipsetasks'/>) that the Eclipse plugin provides. Some tasks
-        are preconfigured to use the <literal>GRADLE_CACHE</literal> eclipse classpath variable. To make use of this variable
-        you need to define it in Eclipse pointing to <literal><USER_HOME>/.gradle/cache</literal>.
-        In a future release of Gradle this will be automated (<ulink url="http://jira.codehaus.org/browse/GRADLE-1074">Issue GRADLE-1074</ulink>).
-</para>
+    <para>There are several tasks that the Eclipse plugin provides (presented in <xref linkend='eclipsetasks'/>). The
+        main task that you'd use is the <literal>eclipse</literal> task.
+    </para>
     </section>
     <section>
         <title>Tasks</title>
@@ -56,8 +75,9 @@
                 <td>
                     <literal>eclipse</literal>
                 </td>
-                <td><literal>eclipseProject</literal>, <literal>eclipseClasspath</literal>, <literal>eclipseWtp</literal></td>
-                <td><literal>-</literal></td>
+                <td><literal>eclipseProject</literal>, <literal>eclipseClasspath</literal>, <literal>eclipseJdt</literal>,
+                    <literal>eclipseWtp</literal></td>
+                <td><apilink class="org.gradle.api.Task"/></td>
                 <td>Generates all the eclipse configuration files</td>
             </tr>
             <tr>
@@ -65,7 +85,8 @@
                     <literal>cleanEclipse</literal>
                 </td>
                 <td>
-                    <literal>cleanEclipseProject</literal>, <literal>cleanEclipseClasspath</literal>, <literal>cleanEclipseWtp</literal>
+                    <literal>cleanEclipseProject</literal>, <literal>cleanEclipseClasspath</literal>, <literal>cleanEclipseJdt</literal>,
+                    <literal>cleanEclipseWtp</literal>
                 </td>
                 <td><apilink class="org.gradle.api.tasks.Delete"/></td>
                 <td>Removes all eclipse configuration files</td>
@@ -92,13 +113,23 @@
             </tr>
             <tr>
                 <td>
+                    <literal>cleanEclipseJdt</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the JDT settings file.</td>
+            </tr>
+            <tr>
+                <td>
                     <literal>cleanEclipseWtp</literal>
                 </td>
                 <td>
                     <literal>-</literal>
                 </td>
                 <td><apilink class="org.gradle.api.tasks.Delete"/></td>
-                <td>Removes the eclipse .settings directory</td>
+                <td>Removes the WTP settings files.</td>
             </tr>
             <tr>
                 <td>
@@ -122,14 +153,23 @@
             </tr>
             <tr>
                 <td>
+                    <literal>eclipseJdt</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.eclipse.EclipseJdt" lang="groovy"/></td>
+                <td>Generates the JDT settings file.</td>
+            </tr>
+            <tr>
+                <td>
                     <literal>eclipseWtp</literal>
                 </td>
                 <td>
                     <literal>-</literal>
                 </td>
                 <td><apilink class="org.gradle.plugins.eclipse.EclipseWtp" lang="groovy"/></td>
-                <td>Generates the <filename>org.eclipse.wst.common.component</filename> and 
-                    <filename>org.eclipse.wst.common.project.facet.core</filename> files.</td>
+                <td>Generates the WTP settings files.</td>
             </tr>
         </table>
 
@@ -147,7 +187,9 @@
                 <td>
                     <literal>projectName</literal>
                 </td>
-                <td>String</td>
+                <td>
+                    <classname>String</classname>
+                </td>
                 <td><literal>project.name</literal></td>
                 <td>The name of the Eclipse project. Must not be null.</td>
             </tr>
@@ -155,15 +197,19 @@
                 <td>
                     <literal>comment</literal>
                 </td>
-                <td>String</td>
-                <td>null</td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td><literal>project.description</literal></td>
                 <td>A comment for the eclipse project.</td>
             </tr>
             <tr>
                 <td>
                     <literal>referencedProjects</literal>
                 </td>
-                <td>Set<String></td>
+                <td>
+                    <classname>Set<String></classname>
+                </td>
                 <td>empty set</td>
                 <td>The referenced projects of the Eclipse project.</td>
             </tr>
@@ -171,7 +217,9 @@
                 <td>
                     <literal>natures</literal>
                 </td>
-                <td>List<String></td>
+                <td>
+                    <classname>List<String></classname>
+                </td>
                 <td>The default is an empty set. Applying Java, Groovy, Scala or War plugin
                 will add additional natures.</td>
                 <td>The natures of the Eclipse project.</td>
@@ -180,7 +228,9 @@
                 <td>
                     <literal>buildCommands</literal>
                 </td>
-                <td>List<BuildCommand></td>
+                <td>
+                    <classname>List<BuildCommand></classname>
+                </td>
                 <td>The default is an empty set. Applying Java, Groovy, Scala or War plugin
                 will add additional build commands.</td>
                 <td>The build commands of the Eclipse project.</td>
@@ -189,7 +239,9 @@
                 <td>
                     <literal>links</literal>
                 </td>
-                <td>Set<Link></td>
+                <td>
+                    <classname>Set<Link></classname>
+                </td>
                 <td>empty set</td>
                 <td>The links for this Eclipse project.</td>
             </tr>
@@ -209,7 +261,7 @@
                 <td>
                     <literal>sourceSets</literal>
                 </td>
-                <td>DomainObjectContainer</td>
+                <td><classname>Iterable<SourceSet></classname></td>
                 <td>
                     <literal>sourceSets</literal>
                 </td>
@@ -219,7 +271,9 @@
                 <td>
                     <literal>containers</literal>
                 </td>
-                <td>Set<String></td>
+                <td>
+                    <classname>Set<String></classname>
+                </td>
                 <td>empty set</td>
                 <td>The containers to be added to the Eclipse classpath.</td>
             </tr>
@@ -227,7 +281,9 @@
                 <td>
                     <literal>plusConfigurations</literal>
                 </td>
-                <td>Set<Configuration></td>
+                <td>
+                    <classname>Set<Configuration></classname>
+                </td>
                 <td><literal>[configurations.testRuntime]</literal></td>
                 <td>The configurations, which files are to be transformed into classpath entries.</td>
             </tr>
@@ -235,32 +291,44 @@
                 <td>
                     <literal>minusConfigurations</literal>
                 </td>
-                <td>Set<Configuration></td>
-                <td><literal>empty set</literal></td>
+                <td>
+                    <classname>Set<Configuration></classname>
+                </td>
+                <td>empty set</td>
                 <td>The configurations which files are to be excluded from the classpath entries.</td>
             </tr>
             <tr>
                 <td>
                     <literal>downloadSources</literal>
                 </td>
-                <td>boolean</td>
-                <td>true</td>
+                <td>
+                    <classname>boolean</classname>
+                </td>
+                <td>
+                    <literal>true</literal>
+                </td>
                 <td>Whether to download sources for the external dependencies.</td>
             </tr>
             <tr>
                 <td>
                     <literal>downloadJavadoc</literal>
                 </td>
-                <td>boolean</td>
-                <td>false</td>
+                <td>
+                    <classname>boolean</classname>
+                </td>
+                <td>
+                    <literal>false</literal>
+                </td>
                 <td>Whether to download javadoc for the external dependencies.</td>
             </tr>
             <tr>
                 <td>
                     <literal>variables</literal>
                 </td>
-                <td>Map<String,String></td>
-                <td><literal>[GRADLE_CACHE: <path to gradle cache>]</literal></td>
+                <td>
+                    <classname>Map<String,File></classname>
+                </td>
+                <td><literal>[:]</literal></td>
                 <td>If the beginning of the absolute path of a library matches a value of a variable,
                 a variable entry is created. The matching part of the library path is replaced with the variable name.</td>
             </tr>
@@ -280,9 +348,9 @@
                 <td>
                     <literal>sourceSets</literal>
                 </td>
-                <td>DomainObjectContainer</td>
+                <td><classname>Iterable<SourceSet></classname></td>
                 <td>
-                    <literal>sourceSets</literal>
+                    <literal>project.sourceSets</literal>
                 </td>
                 <td>The source sets which source directories should be added to the Eclipse classpath.</td>
             </tr>
@@ -290,7 +358,9 @@
                 <td>
                     <literal>deployName</literal>
                 </td>
-                <td>String</td>
+                <td>
+                    <classname>String</classname>
+                </td>
                 <td><literal>project.name</literal></td>
                 <td>The deploy name to be used in the org.eclipse.wst.common.component file.</td>
             </tr>
@@ -298,7 +368,9 @@
                 <td>
                     <literal>plusConfigurations</literal>
                 </td>
-                <td>Set<Configuration></td>
+                <td>
+                    <classname>Set<Configuration></classname>
+                </td>
                 <td><literal>[configurations.testRuntime]</literal></td>
                 <td>The configurations, which files are to be transformed into classpath entries.</td>
             </tr>
@@ -306,7 +378,9 @@
                 <td>
                     <literal>minusConfigurations</literal>
                 </td>
-                <td>Set<Configuration></td>
+                <td>
+                    <classname>Set<Configuration></classname>
+                </td>
                 <td><literal>[configurations.providedRuntime]</literal></td>
                 <td>The configurations which files are to be excluded from the classpath entries.</td>
             </tr>
@@ -314,8 +388,10 @@
                 <td>
                     <literal>variables</literal>
                 </td>
-                <td>Map<String,String></td>
-                <td><literal>[GRADLE_CACHE: <path to gradle cache>]</literal></td>
+                <td>
+                    <classname>Map<String,File></classname>
+                </td>
+                <td><literal>[:]</literal></td>
                 <td>If the beginning of the absolute path of a library matches a value of a variable,
                 a variable entry is created. The matching part of the library path is replaced with the variable name.</td>
             </tr>
@@ -323,13 +399,14 @@
                 <td>
                     <literal>facets</literal>
                 </td>
-                <td>List<Facet></td>
+                <td>
+                    <classname>List<Facet></classname>
+                </td>
                 <td><literal>jst.java</literal> and <literal>jst.web</literal> facet</td>
                 <td>The facets to be added as installed elements to the org.eclipse.wst.common.project.facet.core file.</td>
             </tr>
         </table>
 
-
         <table id='eclipse-task-hooks'>
             <title>Task Hooks</title>
             <thead>
@@ -439,9 +516,10 @@
                 <title>Modifying the XML</title>
                 <para>You can also hook into the phase after the XML DOM is fully populated but not written to disk.
                     That hook provides total control over what is generated.
-                    <sample id="file-dependencies" dir="eclipse"
+                    <sample id="wtpWithXml" dir="eclipse"
                             title="Customizing the XML">
                         <sourcefile file="build.gradle" snippet="wtp-with-xml"/>
+                        <test args="eclipse"/>
                     </sample>
                 </para>
             </section>
diff --git a/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml
index 6c15537..8fd56c7 100644
--- a/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml
@@ -21,17 +21,37 @@
         making it possible to open the project from Idea (<guimenuitem>File</guimenuitem> - <guimenuitem>Open Project</guimenuitem>).
         Both external (including associated source and javadoc files) and project dependencies are considered.</para>
 
-    <para>The Idea plugin will create different content depending on what other plugins being used.</para>
-    
-<section>
+    <para>The Idea plugin will create different content depending on which other plugins being used:</para>
+    <table>
+        <title>IDEA support for other plugins</title>
+        <thead>
+            <tr>
+                <td>Plugin</td><td>Description</td>
+            </tr>
+        </thead>
+        <tr>
+            <td>None</td><td>Generates an IDEA module file. Also generates an IDEA project and workspace file if the project
+            is the root project.</td>
+        </tr>
+        <tr>
+            <td>
+                <link linkend="java_plugin">Java</link>
+            </td>
+            <td>Adds java configuration to the module and project files.</td>
+        </tr>
+    </table>
+
+    <para>One focus of the Idea tasks is to be open to customizations. They provide a couple of hooks to add or remove
+        content from the generated files.
+    </para>
+    <section>
         <title>Usage</title>
     <para>To use the Idea plugin, include in your build script:</para>
     <sample id="useIdeaPlugin" dir="idea" title="Using the Idea plugin">
         <sourcefile file="build.gradle" snippet="use-plugin"/>
     </sample>
-    <para>One focus of the Idea tasks is to be open to customizations. They provide a couple of hooks to add or remove content from
-        the generated files.
-</para>
+        <para>The IDEA plugin adds a number of tasks to your project, as discussed below. The main task that you use is
+            the <literal>idea</literal> task.</para>
     </section>
     <section>
         <title>Tasks</title>
@@ -143,7 +163,9 @@
                 <td>
                     <literal>moduleDir</literal>
                 </td>
-                <td>File</td>
+                <td>
+                    <classname>File</classname>
+                </td>
                 <td><literal>projectDir</literal></td>
                 <td><literal>projectDir</literal></td>
                 <td>The content root directory of the module. Must not be null.</td>
@@ -152,8 +174,10 @@
                 <td>
                     <literal>outputFile</literal>
                 </td>
-                <td>File</td>
-                <td><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable><literal>.iml</literal></td>
+                <td>
+                    <classname>File</classname>
+                </td>
+                <td><literal><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable>.iml</literal></td>
                 <td>-</td>
                 <td>The iml file. Used to look for existing files and as the target for generation. Must not be null.</td>
             </tr>
@@ -161,7 +185,9 @@
                 <td>
                     <literal>sourceDirs</literal>
                 </td>
-                <td>Set<File></td>
+                <td>
+                    <classname>Set<File></classname>
+                </td>
                 <td>empty set</td>
                 <td>The source dirs of <literal>sourceSets.main</literal></td>
                 <td>The dirs containing the production sources. Must not be null.</td>
@@ -170,7 +196,9 @@
                 <td>
                     <literal>testSourceDirs</literal>
                 </td>
-                <td>Set<File></td>
+                <td>
+                    <classname>Set<File></classname>
+                </td>
                 <td>empty set</td>
                 <td>The source dirs of <literal>sourceSets.test</literal></td>
                 <td>The dirs containing the test sources. Must not be null.</td>
@@ -179,7 +207,9 @@
                 <td>
                     <literal>excludeDirs</literal>
                 </td>
-                <td>Set<File></td>
+                <td>
+                    <classname>Set<File></classname>
+                </td>
                 <td>empty set</td>
                 <td>-</td>
                 <td>The dirs to be excluded by idea. Must not be null.</td>
@@ -188,8 +218,12 @@
                 <td>
                     <literal>outputDir</literal>
                 </td>
-                <td>File</td>
-                <td>null</td>
+                <td>
+                    <classname>File</classname>
+                </td>
+                <td>
+                    <literal>null</literal>
+                </td>
                 <td><literal>sourceSets.main.classesDir</literal></td>
                 <td>The idea output dir for the production sources. If null no entry for output dirs is created.</td>
             </tr>
@@ -197,8 +231,12 @@
                 <td>
                     <literal>testOutputDir</literal>
                 </td>
-                <td>File</td>
-                <td>null</td>
+                <td>
+                    <classname>File</classname>
+                </td>
+                <td>
+                    <literal>null</literal>
+                </td>
                 <td><literal>sourceSets.test.classesDir</literal></td>
                 <td>The idea output dir for the test sources. If null no entry for test output dirs is created.</td>
             </tr>
@@ -206,8 +244,12 @@
                 <td>
                     <literal>javaVersion</literal>
                 </td>
-                <td>String</td>
-                <td>null</td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>null</literal>
+                </td>
                 <td>-</td>
                 <td>If this is null the value of the existing or default ipr XML (inherited) is used. If it is set
                 to <literal>inherited</literal>, the project SDK is used. Otherwise the SDK for the corresponding
@@ -217,8 +259,12 @@
                 <td>
                     <literal>downloadSources</literal>
                 </td>
-                <td>boolean</td>
-                <td>true</td>
+                <td>
+                    <classname>boolean</classname>
+                </td>
+                <td>
+                    <literal>true</literal>
+                </td>
                 <td>-</td>
                 <td>Whether to download and add source jars associated with the dependency jars.</td>
             </tr>
@@ -226,8 +272,12 @@
                 <td>
                     <literal>downloadJavadoc</literal>
                 </td>
-                <td>boolean</td>
-                <td>false</td>
+                <td>
+                    <classname>boolean</classname>
+                </td>
+                <td>
+                    <literal>false</literal>
+                </td>
                 <td>-</td>
                 <td>Whether to download and add javadoc jars associated with the dependency jars. </td>
             </tr>
@@ -235,8 +285,12 @@
                 <td>
                     <literal>scopes</literal>
                 </td>
-                <td>Map</td>
-                <td>[:]</td>
+                <td>
+                    <classname>Map</classname>
+                </td>
+                <td>
+                    <classname>[:]</classname>
+                </td>
                 <td>COMPILE(compile), RUNTIME(runtime - compile), TEST(testRuntime - runtime)</td>
                 <td>The keys of this map are the Idea scopes. Each key points to another map that has two keys, plus and minus.
                     The values of those keys are sets of <link linkend="sub:configurations">Configuration</link> objects. The files of the
@@ -244,23 +298,17 @@
                 </tr>
             <tr>
                 <td>
-                    <literal>gradleCacheVariable</literal>
+                    <literal>variables</literal>
                 </td>
-                <td>String</td>
-                <td>null</td>
-                <td>-</td>
-                <td>If this variable is set, dependencies in the existing iml file will be parsed for this variable. If they use it,
-                    it will be replaced with a path that has the $MODULE_DIR$ variable as a root and
-                    then a relative path to  {@link #gradleCacheHome} . That way Gradle can recognize equal dependencies.</td>
-            </tr>
-            <tr>
                 <td>
-                    <literal>gradleCacheVariable</literal>
+                    <classname>Map<String, File></classname>
                 </td>
-                <td>File</td>
-                <td>null</td>
-                <td>The gradle cache dir</td>
-                <td>This property is used in conjunction with <code>gradleCacheVariable</code></td>
+                <td>
+                    <literal>[:]</literal>
+                </td>
+                <td>-</td>
+                <td>The variables to be used for replacing absolute paths in the iml entries. For example, you might add
+                    a <literal>GRADLE_USER_HOME</literal> variable to point to the Gradle user home dir.</td>
             </tr>
         </table>
      
@@ -279,7 +327,9 @@
                 <td>
                     <literal>subprojects</literal>
                 </td>
-                <td>Set<Project></td>
+                <td>
+                    <classname>Set<Project></classname>
+                </td>
                 <td><literal>rootProject.allprojects</literal></td>
                 <td>-</td>
                 <td>The subprojects that should be mapped to modules in the <literal>ipr</literal>
@@ -290,8 +340,10 @@
                 <td>
                     <literal>outputFile</literal>
                 </td>
-                <td>File</td>
-                <td><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable><literal>.ipr</literal></td>
+                <td>
+                    <classname>File</classname>
+                </td>
+                <td><literal><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable>.ipr</literal></td>
                 <td>-</td>
                 <td>The ipr file. Used to look for existing files and as the target for generation. Must not be null.</td>
             </tr>
@@ -299,8 +351,12 @@
                 <td>
                     <literal>javaVersion</literal>
                 </td>
-                <td>String</td>
-                <td>1.6</td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>1.6</literal>
+                </td>
                 <td>-</td>
                 <td>The java version used for defining the project sdk.</td>
             </tr>
@@ -308,8 +364,12 @@
                 <td>
                     <literal>wildcards</literal>
                 </td>
-                <td>Set<String></td>
-                <td>['!?*.java', '!?*.groovy']</td>
+                <td>
+                    <classname>Set<String></classname>
+                </td>
+                <td>
+                    <literal>['!?*.java', '!?*.groovy']</literal>
+                </td>
                 <td>-</td>
                 <td>The wildcard resource patterns. Must not be null.</td>
             </tr>
@@ -330,8 +390,10 @@
                 <td>
                     <literal>outputFile</literal>
                 </td>
-                <td>File</td>
-                <td><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable><literal>.ipr</literal></td>
+                <td>
+                    <classname>File</classname>
+                </td>
+                <td><literal><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable>.iws</literal></td>
                 <td>-</td>
                 <td>The iws file. Used to look for existing files and as the target for generation. Must not be null.</td>
             </tr>
@@ -355,7 +417,7 @@
                 </td>
                 <td><apilink class="org.gradle.plugins.idea.model.Project" lang="groovy"/></td>
                 <td><apilink class="org.gradle.plugins.idea.model.Module" lang="groovy"/></td>
-                <td>n/a</td>
+                <td><apilink class="org.gradle.plugins.idea.model.Workspace" lang="groovy"/></td>
                 <td>Gets called directly after the domain objects have been populated with the content of the
                     existing XML file (if there is one).</td>
             </tr>
@@ -365,7 +427,7 @@
                 </td>
                 <td><apilink class="org.gradle.plugins.idea.model.Project" lang="groovy"/></td>
                 <td><apilink class="org.gradle.plugins.idea.model.Module" lang="groovy"/></td>
-                <td>n/a</td>
+                <td><apilink class="org.gradle.plugins.idea.model.Workspace" lang="groovy"/></td>
                 <td>Gets called after the domain objects have been populated with the content of the
                     existing XML file and the content from the build script.</td>
             </tr>
@@ -450,9 +512,10 @@
                 <title>Modifying the XML</title>
                 <para>You can also hook into the phase after the XML DOM is fully populated but not written to disk.
                     That hook provides total control over what is generated.
-                    <sample id="file-dependencies" dir="idea"
+                    <sample id="projectWithXml" dir="idea"
                             title="Customizing the XML">
                         <sourcefile file="build.gradle" snippet="project-with-xml"/>
+                        <test args="idea"/>
                     </sample>
                 </para>
             </section>
diff --git a/subprojects/gradle-docs/src/docs/userguide/installation.xml b/subprojects/gradle-docs/src/docs/userguide/installation.xml
index 896f89a..819a357 100644
--- a/subprojects/gradle-docs/src/docs/userguide/installation.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/installation.xml
@@ -83,15 +83,15 @@ some zip front ends for Mac OS X don't restore the file permissions properly.
 
 <screen>
 ------------------------------------------------------------
-Gradle 0.9-rc-2
+Gradle 0.9-rc-3
 ------------------------------------------------------------
 
-Gradle build time: Wednesday, 27 October 2010 5:05:52 PM EST
+Gradle build time: Monday, November 15, 2010 10:51:22 AM EST
 Groovy: 1.7.5
 Ant: Apache Ant version 1.8.1 compiled on April 30 2010
 Ivy: 2.2.0
-JVM: 1.6.0_20 (Sun Microsystems Inc. 16.3-b01)
-OS: Linux 2.6.35-22-generic amd64
+JVM: 1.6.0_20 (Apple Inc. 16.3-b01-279)
+OS: Mac OS X 10.6.4 x86_64
 </screen>
 
 </section>
diff --git a/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml
index 90321a0..2e46304 100644
--- a/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml
@@ -363,7 +363,7 @@
                 </sample>
                 <para>Gradle exclude rules are converted to Maven excludes if possible. Such a conversion is possible if in
                     the Gradle exclude rule the group as well as the module name is specified (as Maven needs both in
-                    contrast to Ivy). Right now excludes-per-configuration are not converted to the Maven POM.
+                    contrast to Ivy). Per-configuration excludes are also included in the Maven POM, if they are convertible.
                 </para>
             </section>
         </section>
diff --git a/subprojects/gradle-docs/src/docs/userguide/standardTasks.xml b/subprojects/gradle-docs/src/docs/userguide/standardTasks.xml
index 569b081..0f45dfa 100644
--- a/subprojects/gradle-docs/src/docs/userguide/standardTasks.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/standardTasks.xml
@@ -47,12 +47,16 @@
             <td>Generates an Eclipse .classpath file.</td>
         </tr>
         <tr>
+            <td><apilink class='org.gradle.plugins.eclipse.EclipseJdt' lang="groovy"/></td>
+            <td>Generates configuration files for Eclipse JDT.</td>
+        </tr>
+        <tr>
             <td><apilink class='org.gradle.plugins.eclipse.EclipseProject' lang="groovy"/></td>
             <td>Generates an Eclipse .project file.</td>
         </tr>
         <tr>
             <td><apilink class='org.gradle.plugins.eclipse.EclipseWtp' lang="groovy"/></td>
-            <td>Generates Eclipse configuration files for Eclipse WTP.</td>
+            <td>Generates configuration files for Eclipse WTP.</td>
         </tr>
         <tr>
             <td><apilink class='org.gradle.api.tasks.Exec'/></td>
diff --git a/subprojects/gradle-docs/src/docs/userguide/userguide.xml b/subprojects/gradle-docs/src/docs/userguide/userguide.xml
index 7eb3fa7..4bf55dc 100644
--- a/subprojects/gradle-docs/src/docs/userguide/userguide.xml
+++ b/subprojects/gradle-docs/src/docs/userguide/userguide.xml
@@ -80,8 +80,6 @@
     <xi:include href='gradleWrapper.xml'/>
     <xi:include href='embedding.xml'/>
     <!-- this is generated -->
-    <xi:include href='../../../build/src/docbook/dsl.xml'/>
-    <!-- this is generated -->
     <xi:include href='../../../build/src/docbook/samplesList.xml'/>
     <xi:include href='potentialTraps.xml'/>
     <xi:include href='commandLine.xml'/>
diff --git a/subprojects/gradle-docs/src/samples/eclipse/build.gradle b/subprojects/gradle-docs/src/samples/eclipse/build.gradle
index 1f0bb10..d0fa226 100644
--- a/subprojects/gradle-docs/src/samples/eclipse/build.gradle
+++ b/subprojects/gradle-docs/src/samples/eclipse/build.gradle
@@ -29,8 +29,8 @@ eclipseProject {
 
 // START SNIPPET wtp-with-xml
 eclipseWtp {
-    withXml { xml ->
-        xml.'org.eclipse.wst.commons.project.facet.core'.fixed.find { it. at facet == 'jst.java' }. at facet = 'jst2.java'
+    withXml { files ->
+        files.'org.eclipse.wst.commons.project.facet.core'.fixed.find { it. at facet == 'jst.java' }. at facet = 'jst2.java'
     }
 }
-// END SNIPPET wtp-with-xml
\ No newline at end of file
+// END SNIPPET wtp-with-xml
diff --git a/subprojects/gradle-docs/src/samples/idea/build.gradle b/subprojects/gradle-docs/src/samples/idea/build.gradle
index 5d45b4b..90e1e53 100644
--- a/subprojects/gradle-docs/src/samples/idea/build.gradle
+++ b/subprojects/gradle-docs/src/samples/idea/build.gradle
@@ -23,14 +23,14 @@ ideaModule {
 ideaProject {
 // END SNIPPET project-with-xml
     beforeConfigured { project ->
-        project.modules.clear()
+        project.modulePaths.clear()
     }
 // END SNIPPET project-before-configured
 // START SNIPPET project-with-xml
-    withXml { root ->
-        root.component.find { it. at name == 'VcsDirectoryMappings' }.mapping. at vcs = 'Git'
+    withXml { provider ->
+        provider.node.component.find { it. at name == 'VcsDirectoryMappings' }.mapping. at vcs = 'Git'
     }
 // START SNIPPET project-before-configured
 }
 // END SNIPPET project-before-configured
-// END SNIPPET project-with-xml
\ No newline at end of file
+// END SNIPPET project-with-xml
diff --git a/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle b/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle
index a32b669..2533045 100644
--- a/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle
+++ b/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle
@@ -8,12 +8,16 @@ apply plugin: 'maven'
 group = 'gradle'
 version = '1.0'
 archivesBaseName = 'mywar'
-buildDirName = 'target'
+buildDir = 'target'
 
 repositories {
     flatDir(dirs: "$projectDir/lib")
 }
 
+configurations {
+  runtime.exclude group: 'excludeGroup2', module: 'excludeArtifact2'
+}
+
 dependencies {
     compile("group1:compile:1.0") {
         exclude(group: 'excludeGroup', module: 'excludeArtifact')
diff --git a/subprojects/gradle-docs/src/samples/osgi/build.gradle b/subprojects/gradle-docs/src/samples/osgi/build.gradle
index ae44005..b9f0c1b 100644
--- a/subprojects/gradle-docs/src/samples/osgi/build.gradle
+++ b/subprojects/gradle-docs/src/samples/osgi/build.gradle
@@ -23,5 +23,6 @@ jar {
         instruction 'Bundle-Activator', 'org.gradle.GradleActivator'
         instruction 'Import-Package', '*'
         instruction 'Export-Package', '*'
+        attributes( 'Built-By': gradle.gradleVersion )
     }
 }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/AbstractXmlGeneratorTask.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/AbstractXmlGeneratorTask.groovy
deleted file mode 100644
index f48cd9b..0000000
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/AbstractXmlGeneratorTask.groovy
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.eclipse
-
-import org.gradle.api.Action
-import org.gradle.api.internal.ConventionTask
-import org.gradle.listener.ListenerBroadcast
-
-/**
- * @author Hans Dockter
- */
-class AbstractXmlGeneratorTask extends ConventionTask {
-    ListenerBroadcast<Action> beforeConfiguredActions = new ListenerBroadcast<Action>(Action.class);
-    ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
-
-    void beforeConfigured(Closure closure) {
-        beforeConfiguredActions.add("execute", closure);
-    }
-
-    void whenConfigured(Closure closure) {
-        whenConfiguredActions.add("execute", closure);
-    }
-
-
-}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy
index fb76500..82bb853 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy
@@ -15,38 +15,23 @@
  */
 package org.gradle.plugins.eclipse
 
-import org.gradle.api.NamedDomainObjectContainer
 import org.gradle.api.artifacts.Configuration
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import org.gradle.plugins.eclipse.model.internal.ModelFactory
-import org.gradle.plugins.eclipse.model.Container
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.XmlGeneratorTask
 import org.gradle.plugins.eclipse.model.Classpath
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.api.artifacts.maven.XmlProvider
-import org.gradle.api.Action
+import org.gradle.plugins.eclipse.model.Container
+import org.gradle.plugins.eclipse.model.internal.ClasspathFactory
 
 /**
  * Generates an Eclipse <i>.classpath</i> file.
  *
  * @author Hans Dockter
  */
-public class EclipseClasspath extends AbstractXmlGeneratorTask {
-    /**
-     * The file that is merged into the to be produced classpath file. This file must not exist.
-     */
-    File inputFile
-
-    @OutputFile
-    /**
-     * The output file where to generate the classpath to.
-     */
-    File outputFile
-
+public class EclipseClasspath extends XmlGeneratorTask<Classpath> {
     /**
      * The source sets to be added to the classpath.
      */
-    NamedDomainObjectContainer sourceSets
+    Iterable<SourceSet> sourceSets
 
     /**
      * The configurations which files are to be transformed into classpath entries.
@@ -69,6 +54,11 @@ public class EclipseClasspath extends AbstractXmlGeneratorTask {
     Set<Container> containers = new LinkedHashSet<Container>();
 
     /**
+     * The default output directory for eclipse generated files, eg classes.
+     */
+    File defaultOutputDir;
+
+    /**
      * Whether to download and add sources associated with the dependency jars. Defaults to true.
      */
     boolean downloadSources = true
@@ -78,18 +68,14 @@ public class EclipseClasspath extends AbstractXmlGeneratorTask {
      */
     boolean downloadJavadoc = false
 
-    protected ModelFactory modelFactory = new ModelFactory()
+    protected ClasspathFactory modelFactory = new ClasspathFactory()
 
-    protected XmlTransformer withXmlActions = new XmlTransformer();
-
-    def EclipseClasspath() {
-        outputs.upToDateWhen { false }
+    @Override protected Classpath create() {
+        return new Classpath(xmlTransformer)
     }
 
-    @TaskAction
-    void generateXml() {
-        Classpath classpath = modelFactory.createClasspath(this)
-        classpath.toXml(getOutputFile())
+    @Override protected void configure(Classpath object) {
+        modelFactory.configure(this, object)
     }
 
     /**
@@ -111,24 +97,4 @@ public class EclipseClasspath extends AbstractXmlGeneratorTask {
         assert variables != null
         this.variables.putAll variables
     }
-
-    /**
-     * Adds a closure to be called when the .classpath XML has been created. The XML is passed to the closure as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The closure can modify the XML.
-     *
-     * @param closure The closure to execute when the .classpath XML has been created.
-     */
-    void withXml(Closure closure) {
-        withXmlActions.addAction(closure);
-    }
-
-    /**
-     * Adds an action to be called when the .classpath XML has been created. The XML is passed to the action as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The action can modify the XML.
-     *
-     * @param action The action to execute when the .classpath XML has been created.
-     */
-    void withXml(Action<? super XmlProvider> action) {
-        withXmlActions.addAction(action);
-    }
 }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseJdt.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseJdt.groovy
new file mode 100644
index 0000000..bee92dc
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseJdt.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.eclipse
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.internal.tasks.generator.PersistableConfigurationObjectGenerator
+import org.gradle.api.tasks.GeneratorTask
+import org.gradle.plugins.eclipse.model.Jdt
+
+/**
+ * Generates the Eclipse JDT settings file {@code .settings/org.eclipse.jdt.core.prefs}.
+ */
+class EclipseJdt extends GeneratorTask<Jdt> {
+    File inputFile
+
+    File outputFile
+
+    JavaVersion sourceCompatibility
+
+    JavaVersion targetCompatibility
+
+    EclipseJdt() {
+        generator = new PersistableConfigurationObjectGenerator<Jdt>() {
+            Jdt create() {
+                return new Jdt()
+            }
+
+            void configure(Jdt jdt) {
+                jdt.sourceCompatibility = getSourceCompatibility()
+                jdt.targetCompatibility = getTargetCompatibility()
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy
index b11174c..99ff423 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy
@@ -15,50 +15,50 @@
  */
 package org.gradle.plugins.eclipse
 
-import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.internal.plugins.IdePlugin
 import org.gradle.api.plugins.GroovyBasePlugin
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.WarPlugin
 import org.gradle.api.plugins.scala.ScalaBasePlugin
 import org.gradle.plugins.eclipse.model.BuildCommand
+import org.gradle.plugins.eclipse.model.Facet
+import org.gradle.api.JavaVersion
 
 /**
  * <p>A plugin which generates Eclipse files.</p>
  *
  * @author Hans Dockter
  */
-public class EclipsePlugin implements Plugin<Project> {
+public class EclipsePlugin extends IdePlugin {
     public static final String ECLIPSE_TASK_NAME = "eclipse";
     public static final String CLEAN_ECLIPSE_TASK_NAME = "cleanEclipse";
     public static final String ECLIPSE_PROJECT_TASK_NAME = "eclipseProject";
     public static final String ECLIPSE_WTP_TASK_NAME = "eclipseWtp";
     public static final String ECLIPSE_CP_TASK_NAME = "eclipseClasspath";
+    public static final String ECLIPSE_JDT_TASK_NAME = "eclipseJdt";
 
-    public void apply(final Project project) {
-        project.apply plugin: 'base' // We apply the base plugin to have the clean<taskname> rule
-        project.task('cleanEclipse') {
-            description = 'Cleans the generated eclipse files.'
-            group = 'IDE'
-        }
-        project.task('eclipse') {
-            description = 'Generates the Eclipse files.'
-            group = 'IDE'
-        }
+    @Override protected String getLifecycleTaskName() {
+        return 'eclipse'
+    }
+
+    @Override protected void onApply(Project project) {
+        lifecycleTask.description = 'Generates the Eclipse files.'
+        cleanTask.description = 'Cleans the generated eclipse files.'
         configureEclipseProject(project)
         configureEclipseClasspath(project)
-        project.plugins.withType(WarPlugin.class).allPlugins {
-            configureEclipseWtpModuleForWarProjects(project);
-        }
+        configureEclipseJdt(project)
+        configureEclipseWtpModuleForWarProjects(project);
     }
 
     private void configureEclipseProject(Project project) {
         EclipseProject eclipseProject = project.tasks.add(ECLIPSE_PROJECT_TASK_NAME, EclipseProject.class);
         eclipseProject.setProjectName(project.name);
-        eclipseProject.description = "Generates an Eclipse .project file."
+        eclipseProject.description = "Generates the Eclipse .project file."
         eclipseProject.setInputFile(project.file('.project'))
         eclipseProject.setOutputFile(project.file('.project'))
+        eclipseProject.conventionMapping.comment = { project.description }
 
         project.plugins.withType(JavaBasePlugin.class).allPlugins {
             project.configure(project.eclipseProject) {
@@ -87,53 +87,74 @@ public class EclipsePlugin implements Plugin<Project> {
             }
         }
 
-        project."$ECLIPSE_TASK_NAME".dependsOn eclipseProject
-        project."$CLEAN_ECLIPSE_TASK_NAME".dependsOn 'cleanEclipseProject'
+        addWorker(eclipseProject)
     }
 
     private void configureEclipseClasspath(final Project project) {
         project.plugins.withType(JavaBasePlugin.class).allPlugins {
             EclipseClasspath eclipseClasspath = project.tasks.add(ECLIPSE_CP_TASK_NAME, EclipseClasspath.class);
             project.configure(eclipseClasspath) {
-                description = "Generates an Eclipse .classpath file."
+                description = "Generates the Eclipse .classpath file."
                 containers 'org.eclipse.jdt.launching.JRE_CONTAINER'
                 sourceSets = project.sourceSets
                 inputFile = project.file('.classpath')
                 outputFile = project.file('.classpath')
-                variables = [GRADLE_CACHE: new File(project.gradle.getGradleUserHomeDir(), 'cache').canonicalPath]
+                conventionMapping.defaultOutputDir = { new File(project.buildDir, 'eclipse') }
             }
-            project."$ECLIPSE_TASK_NAME".dependsOn eclipseClasspath
-            project."$CLEAN_ECLIPSE_TASK_NAME".dependsOn 'cleanEclipseClasspath'
+            addWorker(eclipseClasspath)
         }
         project.plugins.withType(JavaPlugin.class).allPlugins {
             project.configure(project.eclipseClasspath) {
                 plusConfigurations = [project.configurations.testRuntime]
+                conventionMapping.defaultOutputDir = { project.sourceSets.main.classesDir }
+            }
+        }
+    }
+
+    private void configureEclipseJdt(final Project project) {
+        project.plugins.withType(JavaBasePlugin.class).allPlugins {
+            EclipseJdt eclipseJdt = project.tasks.add(ECLIPSE_JDT_TASK_NAME, EclipseJdt.class);
+            project.configure(eclipseJdt) {
+                description = "Generates the Eclipse JDT settings file."
+                outputFile = project.file('.settings/org.eclipse.jdt.core.prefs')
+                inputFile = project.file('.settings/org.eclipse.jdt.core.prefs')
+                conventionMapping.sourceCompatibility = { project.sourceCompatibility }
+                conventionMapping.targetCompatibility = { project.targetCompatibility }
             }
+            addWorker(eclipseJdt)
         }
     }
 
     private void configureEclipseWtpModuleForWarProjects(final Project project) {
-        final EclipseWtp eclipseWtp = project.getTasks().add(ECLIPSE_WTP_TASK_NAME, EclipseWtp.class);
+        project.plugins.withType(WarPlugin.class).allPlugins {
+            final EclipseWtp eclipseWtp = project.getTasks().add(ECLIPSE_WTP_TASK_NAME, EclipseWtp.class);
 
-        project.configure(eclipseWtp) {
-            deployName = project.name
-            facet name: "jst.web", version: "2.4"
-            facet name: "jst.java", version: "1.4"
-            sourceSets = project.sourceSets.matching { sourceSet -> sourceSet.name == 'main' }
-            plusConfigurations = [project.configurations.runtime]
-            minusConfigurations = [project.configurations.providedRuntime]
-            variables = [GRADLE_CACHE: new File(project.gradle.getGradleUserHomeDir(), 'cache').canonicalPath]
-            resource deployPath: '/', sourcePath: project.convention.plugins.war.webAppDirName
-            orgEclipseWstCommonComponentInputFile = project.file('.settings/org.eclipse.wst.common.component.xml')
-            orgEclipseWstCommonComponentOutputFile = project.file('.settings/org.eclipse.wst.common.component.xml')
-            orgEclipseWstCommonProjectFacetCoreInputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-            orgEclipseWstCommonProjectFacetCoreOutputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+            project.configure(eclipseWtp) {
+                description = 'Generate the Eclipse WTP settings files.'
+                deployName = project.name
+                conventionMapping.contextPath = { project.war.baseName }
+                conventionMapping.facets = { [new Facet("jst.web", "2.4"), new Facet("jst.java", toJavaFacetVersion(project.sourceCompatibility))]}
+                sourceSets = project.sourceSets.matching { sourceSet -> sourceSet.name == 'main' }
+                plusConfigurations = [project.configurations.runtime]
+                minusConfigurations = [project.configurations.providedRuntime]
+                resource deployPath: '/', sourcePath: project.convention.plugins.war.webAppDirName
+                orgEclipseWstCommonComponentInputFile = project.file('.settings/org.eclipse.wst.common.component')
+                orgEclipseWstCommonComponentOutputFile = project.file('.settings/org.eclipse.wst.common.component')
+                orgEclipseWstCommonProjectFacetCoreInputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+                orgEclipseWstCommonProjectFacetCoreOutputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+            }
+
+            addWorker(eclipseWtp)
         }
+    }
 
-        project."$ECLIPSE_TASK_NAME".dependsOn eclipseWtp
-        project."$CLEAN_ECLIPSE_TASK_NAME".dependsOn 'cleanEclipseWtp'
-        project.cleanEclipseWtp {
-            delete project.file('.settings')
+    def toJavaFacetVersion(JavaVersion version) {
+        if (version == JavaVersion.VERSION_1_5) {
+            return '5.0'
+        }
+        if (version == JavaVersion.VERSION_1_6) {
+            return '6.0'
         }
+        return version.toString()
     }
 }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy
index fa7fdcd..6285da7 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy
@@ -17,36 +17,20 @@ package org.gradle.plugins.eclipse;
 
 
 import org.gradle.api.InvalidUserDataException
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.XmlGeneratorTask
 import org.gradle.plugins.eclipse.model.BuildCommand
 import org.gradle.plugins.eclipse.model.Link
-import org.gradle.plugins.eclipse.model.internal.ModelFactory
 import org.gradle.plugins.eclipse.model.Project
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.api.artifacts.maven.XmlProvider
-import org.gradle.api.Action
 
 /**
  * Generates an Eclipse <i>.project</i> file.
  *
  * @author Hans Dockter
  */
-public class EclipseProject extends AbstractXmlGeneratorTask {
+public class EclipseProject extends XmlGeneratorTask<Project> {
     private static final LINK_ARGUMENTS = ['name', 'type', 'location', 'locationUri']
 
     /**
-     * The file that is merged into the to be produced project file. This file must not exist.
-     */
-    File inputFile
-
-    @OutputFile
-    /**
-     * The output file where to generate the project metadata to.
-     */
-    File outputFile
-
-    /**
      * The name used for the name of the eclipse project
      */
     String projectName;
@@ -76,18 +60,12 @@ public class EclipseProject extends AbstractXmlGeneratorTask {
      */
     Set<Link> links = new LinkedHashSet<Link>();
 
-    protected ModelFactory modelFactory = new ModelFactory()
-
-    def XmlTransformer withXmlActions = new XmlTransformer();
-
-    def EclipseProject() {
-        outputs.upToDateWhen { false }
+    @Override protected Project create() {
+        return new Project(xmlTransformer)
     }
 
-    @TaskAction
-    void generateXml() {
-        Project project = modelFactory.createProject(this)
-        project.toXml(getOutputFile())
+    @Override protected void configure(Project project) {
+        project.configure(this)
     }
 
     /**
@@ -144,24 +122,4 @@ public class EclipseProject extends AbstractXmlGeneratorTask {
         }
         this.links.add(new Link(args.name, args.type, args.location, args.locationUri))
     }
-
-    /**
-     * Adds a closure to be called when the .project XML has been created. The XML is passed to the closure as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The closure can modify the XML.
-     *
-     * @param closure The closure to execute when the .project XML has been created.
-     */
-    void withXml(Closure closure) {
-        withXmlActions.addAction(closure);
-    }
-
-    /**
-     * Adds an action to be called when the .project XML has been created. The XML is passed to the action as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The action can modify the XML.
-     *
-     * @param action The action to execute when the .project XML has been created.
-     */
-    void withXml(Action<? super XmlProvider> action) {
-        withXmlActions.addAction(action);
-    }
 }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy
index c681855..9bc4e14 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy
@@ -15,24 +15,26 @@
  */
 package org.gradle.plugins.eclipse;
 
-import org.gradle.api.NamedDomainObjectContainer
+
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.ConventionTask
 import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.TaskAction
+import org.gradle.listener.ActionBroadcast
 import org.gradle.plugins.eclipse.model.Facet
-import org.gradle.plugins.eclipse.model.internal.ModelFactory
-import org.gradle.plugins.eclipse.model.Wtp
-import org.gradle.plugins.eclipse.model.WbResource
 import org.gradle.plugins.eclipse.model.WbProperty
-import org.gradle.api.Action
-import org.gradle.listener.ListenerBroadcast
+import org.gradle.plugins.eclipse.model.WbResource
+import org.gradle.plugins.eclipse.model.Wtp
+import org.gradle.plugins.eclipse.model.internal.WtpFactory
+import org.gradle.util.ConfigureUtil
 
 /**
  * Generates Eclipse configuration files for Eclipse WTP.
  *
  * @author Hans Dockter
  */
-public class EclipseWtp extends AbstractXmlGeneratorTask {
+public class EclipseWtp extends ConventionTask {
     /**
      * The file that is merged into the to be produced org.eclipse.wst.common.component file. This
      * file must not exist.
@@ -60,7 +62,7 @@ public class EclipseWtp extends AbstractXmlGeneratorTask {
     /**
      * The source sets to be transformed into wb-resource elements.
      */
-    NamedDomainObjectContainer sourceSets
+    Iterable<SourceSet> sourceSets
 
     /**
      * The configurations which files are to be transformed into dependent-module elements of
@@ -100,9 +102,16 @@ public class EclipseWtp extends AbstractXmlGeneratorTask {
      */
     List<WbProperty> properties = []
 
-    protected ModelFactory modelFactory = new ModelFactory()
+    /**
+     * The context path for the web application
+     */
+    String contextPath
+
+    protected WtpFactory modelFactory = new WtpFactory()
 
-    def ListenerBroadcast<Action> withXmlActions = new ListenerBroadcast<Action>(Action.class);
+    def ActionBroadcast<Map<String, Node>> withXmlActions = new ActionBroadcast<Map<String, Node>>();
+    def ActionBroadcast<Wtp> beforeConfiguredActions = new ActionBroadcast<Wtp>();
+    def ActionBroadcast<Wtp> whenConfiguredActions = new ActionBroadcast<Wtp>();
 
     def EclipseWtp() {
         outputs.upToDateWhen { false }
@@ -120,7 +129,7 @@ public class EclipseWtp extends AbstractXmlGeneratorTask {
      * @param args A map that must contain a name and version key with corresponding values.
      */
     void facet(Map args) {
-        facets.add(new Facet(args.name, args.version))
+        setFacets(getFacets() + [ConfigureUtil.configureByMap(args, new Facet())])
     }
 
     /**
@@ -153,6 +162,14 @@ public class EclipseWtp extends AbstractXmlGeneratorTask {
     }
 
     void withXml(Closure closure) {
-        withXmlActions.add("execute", closure);
+        withXmlActions.add(closure);
+    }
+
+    void beforeConfigured(Closure closure) {
+        beforeConfiguredActions.add(closure);
+    }
+
+    void whenConfigured(Closure closure) {
+        whenConfiguredActions.add(closure);
     }
 }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy
index 0aec4b8..65610b5 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy
@@ -16,46 +16,27 @@
 package org.gradle.plugins.eclipse.model
 
 import org.gradle.api.internal.XmlTransformer
-
-import org.gradle.api.Action
+import org.gradle.api.internal.tasks.generator.XmlPersistableConfigurationObject
 
 /**
  * Represents the customizable elements of an eclipse classpath file. (via XML hooks everything is customizable).
  *
  * @author Hans Dockter
  */
-class Classpath {
-    /**
-     * The classpath entries (contains by default an output entry pointing to bin).
-     */
-    List entries = [new Output('bin')]
-
-    private Node xml
-
-    private XmlTransformer xmlTransformer
-
-    Classpath(Action<Classpath> beforeConfiguredAction, Action<Classpath> whenConfiguredAction, XmlTransformer xmlTransformer, List entries, Reader inputXml) {
-        initFromXml(inputXml)
-
-        beforeConfiguredAction.execute(this)
-
-        this.entries.addAll(entries)
-        this.entries.unique()
-        this.xmlTransformer = xmlTransformer
+class Classpath extends XmlPersistableConfigurationObject {
+    List<ClasspathEntry> entries = []
 
-        whenConfiguredAction.execute(this)
+    Classpath(XmlTransformer xmlTransformer) {
+        super(xmlTransformer)
     }
 
-    private def initFromXml(Reader inputXml) {
-        if (!inputXml) {
-            xml = new Node(null, 'classpath')
-            return
-        }
-
-        xml = new XmlParser().parse(inputXml)
+    @Override protected String getDefaultResourceName() {
+        return 'defaultClasspath.xml'
+    }
 
-        xml.classpathentry.each { entryNode ->
-            def entry = null
+    @Override protected void load(Node xml) {
+        xml.classpathentry.each { Node entryNode ->
+            ClasspathEntry entry = null
             switch (entryNode. at kind) {
                 case 'src':
                     def path = entryNode. at path
@@ -76,18 +57,18 @@ class Classpath {
         }
     }
 
-    void toXml(File file) {
-        file.withWriter { Writer writer -> toXml(writer) }
+    def configure(List entries) {
+        this.entries.addAll(entries)
+        this.entries.unique()
     }
 
-    def toXml(Writer writer) {
+    @Override protected void store(Node xml) {
         xml.classpathentry.each { xml.remove(it) }
         entries.each { ClasspathEntry entry ->
             entry.appendNode(xml)
         }
-        xmlTransformer.transform(xml, writer)
     }
-    
+
     boolean equals(o) {
         if (this.is(o)) { return true }
 
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy
index 3fd862d..9886610 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy
@@ -23,6 +23,9 @@ class Facet {
     String name
     String version
 
+    def Facet() {
+    }
+
     def Facet(Node node) {
         this(node. at facet, node. at version)
     }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Jdt.java b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Jdt.java
new file mode 100644
index 0000000..9df6936
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Jdt.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.eclipse.model;
+
+import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.tasks.generator.PropertiesPersistableConfigurationObject;
+
+import java.util.Properties;
+
+/**
+ * Represents the Eclipse JDT settings.
+ */
+public class Jdt extends PropertiesPersistableConfigurationObject {
+    private JavaVersion sourceCompatibility;
+    private JavaVersion targetCompatibility;
+
+    /**
+     * Sets the source compatibility for the compiler.
+     */
+    public void setSourceCompatibility(JavaVersion sourceCompatibility) {
+        this.sourceCompatibility = sourceCompatibility;
+    }
+
+    /**
+     * Sets the target compatibility for the compiler.
+     */
+    public void setTargetCompatibility(JavaVersion targetCompatibility) {
+        this.targetCompatibility = targetCompatibility;
+    }
+
+    @Override
+    protected String getDefaultResourceName() {
+        return "defaultJdtPrefs.properties";
+    }
+
+    @Override
+    protected void load(Properties properties) {
+    }
+
+    @Override
+    protected void store(Properties properties) {
+        properties.put("org.eclipse.jdt.core.compiler.compliance", sourceCompatibility.toString());
+        properties.put("org.eclipse.jdt.core.compiler.source", sourceCompatibility.toString());
+
+        if (sourceCompatibility.compareTo(JavaVersion.VERSION_1_3) <= 0) {
+            properties.put("org.eclipse.jdt.core.compiler.problem.assertIdentifier", "ignore");
+            properties.put("org.eclipse.jdt.core.compiler.problem.enumIdentifier", "ignore");
+        } else if (sourceCompatibility == JavaVersion.VERSION_1_4) {
+            properties.put("org.eclipse.jdt.core.compiler.problem.assertIdentifier", "error");
+            properties.put("org.eclipse.jdt.core.compiler.problem.enumIdentifier", "warning");
+        } else {
+            properties.put("org.eclipse.jdt.core.compiler.problem.assertIdentifier", "error");
+            properties.put("org.eclipse.jdt.core.compiler.problem.enumIdentifier", "error");
+        }
+
+        properties.put("org.eclipse.jdt.core.compiler.codegen.targetPlatform", targetCompatibility.toString());
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy
index cf7463a..b1f54dc 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy
@@ -17,13 +17,14 @@ package org.gradle.plugins.eclipse.model
 
 import org.gradle.api.internal.XmlTransformer
 import org.gradle.plugins.eclipse.EclipseProject
+import org.gradle.api.internal.tasks.generator.XmlPersistableConfigurationObject
 
 /**
  * Represents the customizable elements of an eclipse project file. (via XML hooks everything is customizable).
  *
  * @author Hans Dockter
  */
-class Project {
+class Project extends XmlPersistableConfigurationObject {
     public static final String PROJECT_FILE_NAME = ".project";
 
     /**
@@ -56,40 +57,15 @@ class Project {
      */
     Set<Link> links = new LinkedHashSet<Link>()
 
-    private Node xml
-
-    private XmlTransformer xmlTransformer
-
-    def Project(EclipseProject eclipseProjectTask, Reader inputXml) {
-        initFromXml(inputXml)
-
-        eclipseProjectTask.beforeConfiguredActions.source.execute(this)
-
-        if (eclipseProjectTask.projectName) {
-            this.name = eclipseProjectTask.projectName
-        }
-        if (eclipseProjectTask.comment) {
-            this.comment = eclipseProjectTask.comment
-        }
-        this.referencedProjects.addAll(eclipseProjectTask.referencedProjects)
-        this.natures.addAll(eclipseProjectTask.natures)
-        this.natures.unique()
-        this.buildCommands.addAll(eclipseProjectTask.buildCommands)
-        this.buildCommands.unique()
-        this.links.addAll(eclipseProjectTask.links);
-        this.xmlTransformer = eclipseProjectTask.withXmlActions
-
-        eclipseProjectTask.whenConfiguredActions.source.execute(this)
+    def Project(XmlTransformer xmlTransformer) {
+        super(xmlTransformer)
     }
 
-    private def initFromXml(Reader inputXml) {
-        if (!inputXml) {
-            xml = new Node(null, 'projectDescription')
-            return
-        }
-
-        xml = new XmlParser().parse(inputXml)
+    @Override protected String getDefaultResourceName() {
+        return 'defaultProject.xml'
+    }
 
+    @Override protected void load(Node xml) {
         this.name = xml.name.text()
         this.comment = xml.comment.text()
         readReferencedProjects()
@@ -124,12 +100,23 @@ class Project {
         }
     }
 
-    void toXml(File file) {
-        file.withWriter {Writer writer -> toXml(writer)}
+    def configure(EclipseProject eclipseProjectTask) {
+        if (eclipseProjectTask.projectName) {
+            this.name = eclipseProjectTask.projectName
+        }
+        if (eclipseProjectTask.comment) {
+            this.comment = eclipseProjectTask.comment
+        }
+        this.referencedProjects.addAll(eclipseProjectTask.referencedProjects)
+        this.natures.addAll(eclipseProjectTask.natures)
+        this.natures.unique()
+        this.buildCommands.addAll(eclipseProjectTask.buildCommands)
+        this.buildCommands.unique()
+        this.links.addAll(eclipseProjectTask.links);
     }
 
-    def toXml(Writer writer) {
-        ['name', 'comment', 'projects', 'natures', 'buildSpec', 'links'].each{ childNodeName ->
+    @Override protected void store(Node xml) {
+        ['name', 'comment', 'projects', 'natures', 'buildSpec', 'links'].each { childNodeName ->
             Node childNode = xml.children().find { it.name() == childNodeName }
             if (childNode) {
                 xml.remove(childNode)
@@ -141,7 +128,6 @@ class Project {
         addNaturesToXml()
         addBuildSpecToXml()
         addLinksToXml()
-        xmlTransformer.transform(xml, writer)
     }
 
     private def addReferencedProjectsToXml() {
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy
index 0ac424e..8cc03f3 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.eclipse.model
 
-import org.gradle.listener.ListenerBroadcast
+import org.gradle.api.Action
 import org.gradle.plugins.eclipse.EclipseWtp
 
 /**
@@ -28,16 +28,18 @@ class Wtp {
 
     String deployName
 
+    String contextPath
+
     private Node orgEclipseWstCommonComponentXml
     private Node orgEclipseWstCommonProjectFacetCoreXml
 
-    private ListenerBroadcast withXmlActions
+    private Action<Map<String, Node>> withXmlActions
 
     Wtp(EclipseWtp eclipseWtp, List wbModuleEntries, Reader inputOrgEclipseWstCommonComponentXml,
         Reader inputOrgEclipseWstCommonProjectFacetCoreXml) {
         initFromXml(inputOrgEclipseWstCommonComponentXml, inputOrgEclipseWstCommonProjectFacetCoreXml)
 
-        eclipseWtp.beforeConfiguredActions.source.execute(this)
+        eclipseWtp.beforeConfiguredActions.execute(this)
 
         this.wbModuleEntries.addAll(wbModuleEntries)
         this.wbModuleEntries.unique()
@@ -46,9 +48,12 @@ class Wtp {
         if (eclipseWtp.deployName) {
             this.deployName = eclipseWtp.deployName
         }
+        if (eclipseWtp.contextPath) {
+            this.contextPath = eclipseWtp.contextPath
+        }
         this.withXmlActions = eclipseWtp.withXmlActions
 
-        eclipseWtp.whenConfiguredActions.source.execute(this)
+        eclipseWtp.whenConfiguredActions.execute(this)
     }
 
     private def initFromXml(Reader inputOrgEclipseWstCommonComponentXml, Reader inputOrgEclipseWstCommonProjectFacetCoreXml) {
@@ -69,11 +74,16 @@ class Wtp {
     private def readOrgEclipseWstCommonComponentXml(Reader inputXml) {
         def rootNode = new XmlParser().parse(inputXml)
 
-        deployName = rootNode.'wb-module'[0].@'deploy-name' 
+        deployName = rootNode.'wb-module'[0].@'deploy-name'
         rootNode.'wb-module'[0].children().each { entryNode ->
             def entry = null
             switch (entryNode.name()) {
-                case 'property': entry = new WbProperty(entryNode)
+                case 'property':
+                    if (entryNode. at name == 'context-root') {
+                        contextPath = entryNode. at value
+                    } else {
+                        entry = new WbProperty(entryNode)
+                    }
                     break
                 case 'wb-resource': entry = new WbResource(entryNode)
                     break
@@ -107,13 +117,14 @@ class Wtp {
     def toXml(Writer orgEclipseWstCommonComponentXmlWriter, Writer orgEclipseWstCommonProjectFacetCoreXmlWriter) {
         removeConfigurableDataFromXml()
         orgEclipseWstCommonComponentXml.'wb-module'[0].@'deploy-name' = deployName
+        new WbProperty('context-root', contextPath).appendNode(orgEclipseWstCommonComponentXml.'wb-module')
         wbModuleEntries.each { entry ->
             entry.appendNode(orgEclipseWstCommonComponentXml.'wb-module')
         }
         facets.each { facet ->
             facet.appendNode(orgEclipseWstCommonProjectFacetCoreXml)
         }
-        withXmlActions.source.execute([
+        withXmlActions.execute([
                 'org.eclipse.wst.commons.component': orgEclipseWstCommonComponentXml,
                 'org.eclipse.wst.commons.project.facet.core': orgEclipseWstCommonProjectFacetCoreXml])
 
@@ -143,6 +154,7 @@ class Wtp {
         Wtp wtp = (Wtp) o;
 
         if (deployName != wtp.deployName) { return false }
+        if (contextPath != wtp.contextPath) { return false }
         if (facets != wtp.facets) { return false }
         if (wbModuleEntries != wtp.wbModuleEntries) { return false }
 
@@ -163,6 +175,7 @@ class Wtp {
                 "wbModuleEntries=" + wbModuleEntries +
                 ", facets=" + facets +
                 ", deployName='" + deployName + '\'' +
+                ", contextPath='" + contextPath + '\'' +
                 '}';
     }
 }
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy
index c3b16bd..6231a99 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy
@@ -27,13 +27,13 @@ import org.gradle.plugins.eclipse.EclipseClasspath
  * @author Hans Dockter
  */
 class ClasspathFactory {
-    Classpath createClasspath(EclipseClasspath eclipseClasspath) {
-        File inputFile = eclipseClasspath.inputFile
-        FileReader inputReader = inputFile != null && inputFile.exists() ? new FileReader(inputFile) : null
-        List entries = getEntriesFromSourceSets(eclipseClasspath.sourceSets, eclipseClasspath.project)
+    def configure(EclipseClasspath eclipseClasspath, Classpath classpath) {
+        List entries = []
+        entries.add(new Output(eclipseClasspath.project.relativePath(eclipseClasspath.defaultOutputDir)))
+        entries.addAll(getEntriesFromSourceSets(eclipseClasspath.sourceSets, eclipseClasspath.project))
         entries.addAll(getEntriesFromContainers(eclipseClasspath.getContainers()))
         entries.addAll(getEntriesFromConfigurations(eclipseClasspath))
-        return new Classpath(eclipseClasspath.beforeConfiguredActions.source, eclipseClasspath.whenConfiguredActions.source, eclipseClasspath.withXmlActions, entries, inputReader)
+        classpath.configure(entries)
     }
 
     List getEntriesFromSourceSets(def sourceSets, def project) {
@@ -63,7 +63,7 @@ class ClasspathFactory {
     }
 
     List getEntriesFromConfigurations(EclipseClasspath eclipseClasspath) {
-        getModules(eclipseClasspath) + getLibraries(eclipseClasspath) 
+        getModules(eclipseClasspath) + getLibraries(eclipseClasspath)
     }
 
     protected List getModules(EclipseClasspath eclipseClasspath) {
@@ -112,10 +112,10 @@ class ClasspathFactory {
     }
 
     AbstractLibrary createLibraryEntry(File binary, File source, File javadoc, Map variables) {
-        def usedVariableEntry = variables.find { name, value -> binary.canonicalPath.startsWith(value) }
+        def usedVariableEntry = variables.find { String name, File value -> binary.canonicalPath.startsWith(value.canonicalPath) }
         if (usedVariableEntry) {
             String name = usedVariableEntry.key
-            String value = usedVariableEntry.value
+            String value = usedVariableEntry.value.canonicalPath
             String binaryPath = name + binary.canonicalPath.substring(value.length())
             String sourcePath = source ? name + source.canonicalPath.substring(value.length()) : null
             String javadocPath = javadoc ? name + javadoc.canonicalPath.substring(value.length()) : null
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ModelFactory.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ModelFactory.groovy
deleted file mode 100644
index 05e4b0b..0000000
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ModelFactory.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.eclipse.model.internal
-
-import org.gradle.plugins.eclipse.model.Classpath
-import org.gradle.plugins.eclipse.model.Project
-import org.gradle.plugins.eclipse.model.Wtp
-import org.gradle.plugins.eclipse.EclipseProject
-import org.gradle.plugins.eclipse.EclipseClasspath
-import org.gradle.plugins.eclipse.EclipseWtp
-
-/**
- * @author Hans Dockter
- */
-class ModelFactory {
-    private ClasspathFactory classpathFactory = new ClasspathFactory()
-    private WtpFactory wtpFactory = new WtpFactory()
-
-    Project createProject(EclipseProject eclipseProject) {
-        File inputFile = eclipseProject.getInputFile()
-        FileReader inputReader = inputFile != null && inputFile.exists() ? new FileReader(inputFile) : null
-        return new Project(eclipseProject, inputReader)
-    }
-
-    Classpath createClasspath(EclipseClasspath eclipseClasspath) {
-        classpathFactory.createClasspath(eclipseClasspath)
-    }
-
-    Wtp createWtp(EclipseWtp eclipseWtp) {
-        wtpFactory.createWtp(eclipseWtp)
-    }
-}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy
index a2b2aa4..bdf9e85 100644
--- a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy
@@ -91,12 +91,12 @@ class WtpFactory {
     }
 
     WbDependentModule createWbDependentModuleEntry(File file, Map variables) {
-        def usedVariableEntry = variables.find { name, value -> file.canonicalPath.startsWith(value) }
+        def usedVariableEntry = variables.find { name, value -> file.canonicalPath.startsWith(value.canonicalPath) }
         String handleSnippet = null;
         if (usedVariableEntry) {
-            handleSnippet = "var/$usedVariableEntry.key/${file.canonicalPath.substring(usedVariableEntry.value.length())}"
+            handleSnippet = "var/$usedVariableEntry.key/${file.canonicalPath.substring(usedVariableEntry.value.canonicalPath.length())}"
         } else {
-            handleSnippet = "lib/${file.canonicalPath.substring(usedVariableEntry.value.length())}"
+            handleSnippet = "lib/${file.canonicalPath}"
         }
         handleSnippet = FilenameUtils.separatorsToUnix(handleSnippet)
         return new WbDependentModule('/WEB-INF/lib', "module:/classpath/$handleSnippet")
diff --git a/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultClasspath.xml b/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultClasspath.xml
new file mode 100644
index 0000000..3923e61
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultClasspath.xml
@@ -0,0 +1 @@
+<classpath/>
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultJdtPrefs.properties b/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultJdtPrefs.properties
new file mode 100644
index 0000000..416f4fb
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultJdtPrefs.properties
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultProject.xml b/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultProject.xml
new file mode 100644
index 0000000..cee5613
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/resources/org/gradle/plugins/eclipse/model/defaultProject.xml
@@ -0,0 +1 @@
+<projectDescription/>
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy
index fab0fb2..13ade92 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy
@@ -18,8 +18,6 @@ package org.gradle.plugins.eclipse;
 
 import org.gradle.api.internal.ConventionTask
 import org.gradle.api.tasks.AbstractSpockTaskTest
-import org.gradle.plugins.eclipse.model.Classpath
-import org.gradle.plugins.eclipse.model.internal.ModelFactory
 
 /**
  * @author Hans Dockter
@@ -53,18 +51,4 @@ public class EclipseClasspathTest extends AbstractSpockTaskTest {
         then:
         eclipseClasspath.variables == [variable1: 'value1', variable2: 'value2']
     }
-
-    def generateXml() {
-        ModelFactory modelFactory = Mock()
-        Classpath classpath = Mock()
-        eclipseClasspath.modelFactory = modelFactory
-        eclipseClasspath.setOutputFile(new File('nonexisting'))
-        modelFactory.createClasspath(eclipseClasspath) >> classpath
-
-        when:
-        eclipseClasspath.generateXml()
-
-        then:
-        1 * classpath.toXml(eclipseClasspath.getOutputFile())
-    }
 }
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy
index 53821c7..38adff7 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy
@@ -54,7 +54,12 @@ class EclipsePluginTest extends Specification {
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
         checkEclipseProjectTask([new BuildCommand('org.eclipse.jdt.core.javabuilder')], ['org.eclipse.jdt.core.javanature'])
         checkEclipseClasspath([] as Set)
+        checkEclipseJdt()
+
+        when:
         project.apply(plugin: 'java')
+
+        then:
         checkEclipseClasspath([project.configurations.testRuntime] as Set)
     }
 
@@ -67,7 +72,6 @@ class EclipsePluginTest extends Specification {
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseWtp)
-        project.cleanEclipseWtp.targetFiles.contains(project.file('.settings'))
         checkEclipseProjectTask([
                 new BuildCommand('org.eclipse.jdt.core.javabuilder'),
                 new BuildCommand('org.eclipse.wst.common.project.facet.core.builder'),
@@ -90,7 +94,11 @@ class EclipsePluginTest extends Specification {
         checkEclipseProjectTask([new BuildCommand('ch.epfl.lamp.sdt.core.scalabuilder')],
                 ['ch.epfl.lamp.sdt.core.scalanature', 'org.eclipse.jdt.core.javanature'])
         checkEclipseClasspath([] as Set)
+
+        when:
         project.apply(plugin: 'scala')
+
+        then:
         checkEclipseClasspath([project.configurations.testRuntime] as Set)
     }
 
@@ -105,7 +113,11 @@ class EclipsePluginTest extends Specification {
         checkEclipseProjectTask([new BuildCommand('org.eclipse.jdt.core.javabuilder')], ['org.eclipse.jdt.groovy.core.groovyNature',
                 'org.eclipse.jdt.core.javanature'])
         checkEclipseClasspath([] as Set)
+
+        when:
         project.apply(plugin: 'groovy')
+
+        then:
         checkEclipseClasspath([project.configurations.testRuntime] as Set)
     }
 
@@ -119,7 +131,6 @@ class EclipsePluginTest extends Specification {
         assert eclipseProjectTask.referencedProjects == [] as Set
         assert eclipseProjectTask.comment == null
         assert eclipseProjectTask.projectName == project.name
-        assert eclipseProjectTask.inputFile == project.file('.project')
         assert eclipseProjectTask.outputFile == project.file('.project')
     }
 
@@ -131,9 +142,22 @@ class EclipsePluginTest extends Specification {
         assert eclipseClasspath.plusConfigurations == configurations
         assert eclipseClasspath.minusConfigurations == [] as Set
         assert eclipseClasspath.containers == ['org.eclipse.jdt.launching.JRE_CONTAINER'] as Set
-        assert eclipseClasspath.inputFile == project.file('.classpath')
         assert eclipseClasspath.outputFile == project.file('.classpath')
-        assert eclipseClasspath.variables == [GRADLE_CACHE: new File(project.gradle.gradleUserHomeDir, 'cache').canonicalPath]
+        def mainSourceSet = project.sourceSets.findByName('main')
+        if (mainSourceSet != null) {
+            assert eclipseClasspath.defaultOutputDir == mainSourceSet.classesDir
+        } else {
+            assert eclipseClasspath.defaultOutputDir == new File(project.buildDir, 'eclipse')
+        }
+        assert eclipseClasspath.variables == [:]
+    }
+
+    private void checkEclipseJdt() {
+        EclipseJdt eclipseJdt = project.eclipseJdt
+        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseJdt)
+        assert eclipseJdt.sourceCompatibility == project.sourceCompatibility
+        assert eclipseJdt.targetCompatibility == project.targetCompatibility
+        assert eclipseJdt.outputFile == project.file('.settings/org.eclipse.jdt.core.prefs')
     }
 
     private void checkEclipseWtp() {
@@ -144,12 +168,13 @@ class EclipsePluginTest extends Specification {
         assert eclipseWtp.plusConfigurations == [project.configurations.runtime] as Set
         assert eclipseWtp.minusConfigurations == [project.configurations.providedRuntime] as Set
         assert eclipseWtp.deployName == project.name
-        assert eclipseWtp.orgEclipseWstCommonComponentInputFile == project.file('.settings/org.eclipse.wst.common.component.xml')
-        assert eclipseWtp.orgEclipseWstCommonComponentOutputFile == project.file('.settings/org.eclipse.wst.common.component.xml')
+        assert eclipseWtp.contextPath == project.war.baseName
+        assert eclipseWtp.orgEclipseWstCommonComponentInputFile == project.file('.settings/org.eclipse.wst.common.component')
+        assert eclipseWtp.orgEclipseWstCommonComponentOutputFile == project.file('.settings/org.eclipse.wst.common.component')
         assert eclipseWtp.orgEclipseWstCommonProjectFacetCoreInputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
         assert eclipseWtp.orgEclipseWstCommonProjectFacetCoreOutputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-        assert eclipseWtp.facets == [new Facet("jst.web", "2.4"), new Facet("jst.java", "1.4")]
-        assert eclipseWtp.variables == [GRADLE_CACHE: new File(project.gradle.gradleUserHomeDir, 'cache').canonicalPath]
+        assert eclipseWtp.facets == [new Facet("jst.web", "2.4"), new Facet("jst.java", "5.0")]
+        assert eclipseWtp.variables == [:]
         assert eclipseWtp.resources == [new WbResource('/', project.convention.plugins.war.webAppDirName)]
     }
 
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy
index 1504827..a578e91 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy
@@ -18,8 +18,6 @@ package org.gradle.plugins.eclipse
 import org.gradle.api.internal.ConventionTask
 import org.gradle.api.tasks.AbstractSpockTaskTest
 import org.gradle.plugins.eclipse.model.BuildCommand
-import org.gradle.plugins.eclipse.model.Project
-import org.gradle.plugins.eclipse.model.internal.ModelFactory
 
 /**
  * @author Hans Dockter
@@ -52,18 +50,4 @@ class EclipseProjectTest extends AbstractSpockTaskTest {
         then:
         eclipseProject.buildCommands as List == [new BuildCommand('command1', [key1: 'value1']), new BuildCommand('command2')]
     }
-
-    def generateXml() {
-        ModelFactory modelFactory = Mock()
-        Project project = Mock()
-        eclipseProject.modelFactory = modelFactory
-        eclipseProject.setOutputFile(new File('nonexisting'))
-        modelFactory.createProject(eclipseProject) >> project
-
-        when:
-        eclipseProject.generateXml()
-
-        then:
-        1 * project.toXml(eclipseProject.getOutputFile())
-    }
 }
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy
index 0494243..ee80d58 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy
@@ -19,12 +19,12 @@ package org.gradle.plugins.eclipse;
 import org.gradle.api.internal.ConventionTask
 import org.gradle.api.tasks.AbstractSpockTaskTest
 import org.gradle.plugins.eclipse.model.Facet
+import org.gradle.plugins.eclipse.model.WbProperty
+import org.gradle.plugins.eclipse.model.WbResource
 import org.gradle.plugins.eclipse.model.Wtp
-import org.gradle.plugins.eclipse.model.internal.ModelFactory
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
-import org.gradle.plugins.eclipse.model.WbProperty
-import org.gradle.plugins.eclipse.model.WbResource
+import org.gradle.plugins.eclipse.model.internal.WtpFactory
 
 /**
  * @author Hans Dockter
@@ -71,7 +71,7 @@ public class EclipseWtpTest extends AbstractSpockTaskTest {
     }
 
     def generateXml() {
-        ModelFactory modelFactory = Mock()
+        WtpFactory modelFactory = Mock()
         Wtp wtp = Mock()
         eclipseWtp.modelFactory = modelFactory
         eclipseWtp.setOrgEclipseWstCommonComponentOutputFile(tmpDir.file("component"))
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy
index 4b32441..50ceb28 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy
@@ -16,8 +16,6 @@
 package org.gradle.plugins.eclipse.model;
 
 
-import org.gradle.api.Action
-import org.gradle.api.artifacts.maven.XmlProvider
 import org.gradle.api.internal.XmlTransformer
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
@@ -28,7 +26,6 @@ import spock.lang.Specification
  */
 
 public class ClasspathTest extends Specification {
-    private static final DEFAULT_ENTRIES = [new Output('bin')]
     private static final CUSTOM_ENTRIES = [
             new ProjectDependency("/test2", false, null, [] as Set),
             new Container("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6",
@@ -36,136 +33,65 @@ public class ClasspathTest extends Specification {
             new Library("/apache-ant-1.7.1/lib/ant-antlr.jar", false, null, [] as Set, null, null),
             new SourceFolder("src", null, [] as Set, "bin2", [], []),
             new Variable("GRADLE_CACHE/ant-1.6.5.jar", false, null, [] as Set, null, null),
-            new Container("org.eclipse.jdt.USER_LIBRARY/gradle", false, null, [] as Set)]
+            new Container("org.eclipse.jdt.USER_LIBRARY/gradle", false, null, [] as Set),
+            new Output("bin")]
+    final Classpath classpath = new Classpath(new XmlTransformer())
 
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
 
-    def initWithReader() {
-        Classpath classpath = createClasspath(reader: customClasspathReader)
-
-        expect:
-        classpath.entries == DEFAULT_ENTRIES + CUSTOM_ENTRIES
-
-    }
-
-    def initWithReaderAndValues_shouldBeMerged() {
-        def constructorDefaultOutput = 'build'
-        def constructorEntries = [createSomeLibrary()]
-
-        Classpath classpath = createClasspath(entries: constructorEntries + [CUSTOM_ENTRIES[0]], defaultOutput: constructorDefaultOutput,
-                reader: customClasspathReader)
-
-        expect:
-        classpath.entries == DEFAULT_ENTRIES + CUSTOM_ENTRIES + constructorEntries
-    }
-
-    def initWithNullReader() {
-        def constructorDefaultOutput = 'build'
-        def constructorEntries = [createSomeLibrary()]
-
-        Classpath classpath = createClasspath(entries: constructorEntries, defaultOutput: constructorDefaultOutput)
-
-        expect:
-        classpath.xml != null
-        classpath.entries == DEFAULT_ENTRIES + constructorEntries
-    }
-
-    def toXml() {
+    def loadFromReader() {
         when:
-        Classpath classpath = createClasspath(reader: customClasspathReader)
+        classpath.load(customClasspathReader)
 
         then:
-        File eclipseFile = tmpDir.file("eclipse.xml")
-        classpath.toXml(eclipseFile)
-        StringWriter stringWriterFileXml = new StringWriter()
-        new XmlNodePrinter(new PrintWriter(stringWriterFileXml)).print(new XmlParser().parse(eclipseFile))
-        StringWriter stringWriterWrittenXml = new StringWriter()
-        new XmlNodePrinter(new PrintWriter(stringWriterWrittenXml)).print(new XmlParser().parse(getToXmlReader(classpath)))
-        StringWriter stringWriterInternalXml = new StringWriter()
-        new XmlNodePrinter(new PrintWriter(stringWriterInternalXml)).print(classpath.xml)
-
-        stringWriterWrittenXml.toString() == stringWriterInternalXml.toString()
-        stringWriterWrittenXml.toString() == stringWriterFileXml.toString()
+        classpath.entries == CUSTOM_ENTRIES
     }
 
-    def toXml_shouldContainCustomValues() {
-        def constructorEntries = [createSomeLibrary()]
-
-        when:
-        Classpath classpath = createClasspath(entries: constructorEntries,
-                reader: customClasspathReader)
-        def classpathFromXml = createClasspath(reader: getToXmlReader(classpath))
-
-        then:
-        classpath == classpathFromXml
-    }
-
-    def beforeConfigured() {
+    def configureMergesEntries() {
         def constructorEntries = [createSomeLibrary()]
-        Action beforeConfiguredAction = { Classpath classpath ->
-            classpath.entries.clear()
-        } as Action
 
         when:
-        Classpath classpath = createClasspath(entries: constructorEntries, reader: customClasspathReader,
-                beforeConfiguredActions: beforeConfiguredAction)
+        classpath.load(customClasspathReader)
+        classpath.configure(constructorEntries + [CUSTOM_ENTRIES[0]])
 
         then:
-        createClasspath(reader: getToXmlReader(classpath)).entries == DEFAULT_ENTRIES + constructorEntries
+        classpath.entries == CUSTOM_ENTRIES + constructorEntries
     }
 
-    def whenConfigured() {
-        def constructorEntry = createSomeLibrary()
-        def configureActionEntry = createSomeLibrary()
-        configureActionEntry.path = constructorEntry.path + 'Other'
-
-        Action whenConfiguredActions = { Classpath classpath ->
-            assert classpath.entries.contains((CUSTOM_ENTRIES as List)[0])
-            assert classpath.entries.contains(constructorEntry)
-            classpath.entries.add(configureActionEntry)
-        } as Action
-
+    def loadDefaults() {
         when:
-        Classpath classpath = createClasspath(entries: [constructorEntry], reader: customClasspathReader,
-                whenConfiguredActions: whenConfiguredActions)
+        classpath.loadDefaults()
 
         then:
-        createClasspath(reader: getToXmlReader(classpath)).entries == DEFAULT_ENTRIES + CUSTOM_ENTRIES +
-                ([constructorEntry, configureActionEntry])
+        classpath.entries == []
     }
 
-    def withXml() {
-        XmlTransformer withXmlActions = new XmlTransformer()
-        Classpath classpath = createClasspath(reader: customClasspathReader, withXmlActions: withXmlActions)
+    def toXml_shouldContainCustomValues() {
+        def constructorEntries = [createSomeLibrary()]
 
         when:
-        withXmlActions.addAction { XmlProvider xml ->
-            xml.asNode().classpathentry.find { it. at kind == 'output' }. at path = 'newPath'
-        }
+        classpath.load(customClasspathReader)
+        classpath.configure(constructorEntries)
+        def xml = getToXmlReader()
+        def other = new Classpath(new XmlTransformer())
+        other.load(xml)
 
         then:
-        new XmlParser().parse(getToXmlReader(classpath)).classpathentry.find { it. at kind == 'output' }. at path == 'newPath'
+        classpath == other
     }
 
-    private InputStreamReader getCustomClasspathReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('customClasspath.xml'))
+    private InputStream getCustomClasspathReader() {
+        return getClass().getResourceAsStream('customClasspath.xml')
     }
 
     private Library createSomeLibrary() {
         return new Library("/somepath", true, null, [] as Set, null, null)
     }
 
-    private Classpath createClasspath(Map customArgs) {
-        Action dummyBroadcast = Mock()
-        XmlTransformer transformer = new XmlTransformer()
-        Map args = [entries: [], reader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: transformer] + customArgs
-        return new Classpath(args.beforeConfiguredActions, args.whenConfiguredActions, args.withXmlActions, args.entries, args.reader)
-    }
-
-    private StringReader getToXmlReader(Classpath classpath) {
-        StringWriter toXmlText = new StringWriter()
-        classpath.toXml(toXmlText)
-        return new StringReader(toXmlText.toString())
+    private InputStream getToXmlReader() {
+        ByteArrayOutputStream toXmlText = new ByteArrayOutputStream()
+        classpath.store(toXmlText)
+        return new ByteArrayInputStream(toXmlText.toByteArray())
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/JdtTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/JdtTest.groovy
new file mode 100644
index 0000000..201254d
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/JdtTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.eclipse.model
+
+import spock.lang.Specification
+import org.gradle.api.JavaVersion
+
+class JdtTest extends Specification {
+    final Jdt jdt = new Jdt()
+
+    def defaultsForJava1_3Source() {
+        Properties properties = new Properties()
+
+        when:
+        jdt.loadDefaults()
+        jdt.sourceCompatibility = JavaVersion.VERSION_1_3
+        jdt.targetCompatibility = JavaVersion.VERSION_1_3
+        store(properties)
+
+        then:
+        properties['org.eclipse.jdt.core.compiler.compliance'] == '1.3'
+        properties['org.eclipse.jdt.core.compiler.source'] == '1.3'
+        properties['org.eclipse.jdt.core.compiler.problem.assertIdentifier'] == 'ignore'
+        properties['org.eclipse.jdt.core.compiler.problem.enumIdentifier'] == 'ignore'
+        properties['org.eclipse.jdt.core.compiler.codegen.targetPlatform'] == '1.3'
+    }
+
+    def defaultsForJava1_4Source() {
+        Properties properties = new Properties()
+
+        when:
+        jdt.loadDefaults()
+        jdt.sourceCompatibility = JavaVersion.VERSION_1_4
+        jdt.targetCompatibility = JavaVersion.VERSION_1_4
+        store(properties)
+
+        then:
+        properties['org.eclipse.jdt.core.compiler.compliance'] == '1.4'
+        properties['org.eclipse.jdt.core.compiler.source'] == '1.4'
+        properties['org.eclipse.jdt.core.compiler.problem.assertIdentifier'] == 'error'
+        properties['org.eclipse.jdt.core.compiler.problem.enumIdentifier'] == 'warning'
+        properties['org.eclipse.jdt.core.compiler.codegen.targetPlatform'] == '1.4'
+    }
+
+    def defaultsForJava1_5Source() {
+        Properties properties = new Properties()
+
+        when:
+        jdt.loadDefaults()
+        jdt.sourceCompatibility = JavaVersion.VERSION_1_5
+        jdt.targetCompatibility = JavaVersion.VERSION_1_5
+        store(properties)
+
+        then:
+        properties['org.eclipse.jdt.core.compiler.compliance'] == '1.5'
+        properties['org.eclipse.jdt.core.compiler.source'] == '1.5'
+        properties['org.eclipse.jdt.core.compiler.problem.assertIdentifier'] == 'error'
+        properties['org.eclipse.jdt.core.compiler.problem.enumIdentifier'] == 'error'
+        properties['org.eclipse.jdt.core.compiler.codegen.targetPlatform'] == '1.5'
+    }
+
+    def defaultsForJava1_6Source() {
+        Properties properties = new Properties()
+
+        when:
+        jdt.loadDefaults()
+        jdt.sourceCompatibility = JavaVersion.VERSION_1_6
+        jdt.targetCompatibility = JavaVersion.VERSION_1_6
+        store(properties)
+
+        then:
+        properties['org.eclipse.jdt.core.compiler.compliance'] == '1.6'
+        properties['org.eclipse.jdt.core.compiler.source'] == '1.6'
+        properties['org.eclipse.jdt.core.compiler.problem.assertIdentifier'] == 'error'
+        properties['org.eclipse.jdt.core.compiler.problem.enumIdentifier'] == 'error'
+        properties['org.eclipse.jdt.core.compiler.codegen.targetPlatform'] == '1.6'
+    }
+
+    def store(Properties properties) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
+        jdt.store(outputStream)
+        properties.load(new ByteArrayInputStream(outputStream.toByteArray()))
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy
index 1810266..08b95f4 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy
@@ -16,14 +16,12 @@
 package org.gradle.plugins.eclipse.model;
 
 
-import org.gradle.api.Action
-import org.gradle.listener.ListenerBroadcast
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.plugins.eclipse.EclipseProject
+import org.gradle.util.HelperUtil
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.plugins.eclipse.EclipseProject
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.api.artifacts.maven.XmlProvider
 
 /**
  * @author Hans Dockter
@@ -36,11 +34,13 @@ public class ProjectTest extends Specification {
 
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
+    final Project project = new Project(new XmlTransformer())
 
-    def initWithReader() {
-        Project project = createProject(reader: customProjectReader)
+    def loadFromReader() {
+        when:
+        project.load(customProjectReader)
 
-        expect:
+        then:
         project.name == 'test'
         project.comment == 'for testing'
         project.referencedProjects == CUSTOM_REFERENCED_PROJECTS
@@ -49,154 +49,65 @@ public class ProjectTest extends Specification {
         project.links == CUSTOM_LINKS
     }
 
-    def initWithReaderAndValues_shouldBeMerged() {
-        def constructorName = 'constructorName'
-        def constructorComment = 'constructorComment'
-        def constructorReferencedProjects = ['constructorRefProject'] as LinkedHashSet
-        def constructorBuildCommands = [new BuildCommand('constructorbuilder')] 
-        def constructorNatures = ['constructorNature']
-        def constructorLinks = [new Link('constructorName', 'constructorType', 'constructorLocation', '')] as Set
-
-        Project project = createProject(name: constructorName, comment: constructorComment, referencedProjects: constructorReferencedProjects,
-                natures: constructorNatures, buildCommands: constructorBuildCommands, links: constructorLinks, reader: customProjectReader)
-
-        expect:
-        project.name == constructorName
-        project.comment == constructorComment
-        project.referencedProjects == constructorReferencedProjects + CUSTOM_REFERENCED_PROJECTS
-        project.buildCommands == CUSTOM_BUILD_COMMANDS + constructorBuildCommands
-        project.natures == CUSTOM_NATURES + constructorNatures 
-        project.links == constructorLinks + CUSTOM_LINKS
-    }
-
-    def initWithNullReader() {
-        def constructorName = 'constructorName'
-        def constructorComment = 'constructorComment'
-        def constructorReferencedProjects = ['constructorRefProject'] as Set
-        def constructorBuildCommands = [new BuildCommand('constructorbuilder')]
-        def constructorNatures = ['constructorNature'] 
-        def constructorLinks = [new Link('constructorName', 'constructorType', 'constructorLocation', '')] as Set
-
-        Project project = createProject(name: constructorName, comment: constructorComment, referencedProjects: constructorReferencedProjects,
-                natures: constructorNatures, buildCommands: constructorBuildCommands, links: constructorLinks)
-
-        expect:
-        project.xml != null
-        project.name == constructorName
-        project.comment == constructorComment
-        project.referencedProjects == constructorReferencedProjects
-        project.buildCommands == constructorBuildCommands
-        project.natures == constructorNatures
-        project.links == constructorLinks
-    }
+    def configureMergesValues() {
+        EclipseProject task = HelperUtil.createTask(EclipseProject)
+        task.projectName = 'constructorName'
+        task.comment = 'constructorComment'
+        task.referencedProjects = ['constructorRefProject'] as LinkedHashSet
+        task.buildCommands = [new BuildCommand('constructorbuilder')]
+        task.natures = ['constructorNature']
+        task.links = [new Link('constructorName', 'constructorType', 'constructorLocation', '')] as Set
 
-    def toXml() {
         when:
-        Project project = createProject(reader: customProjectReader)
+        project.load(customProjectReader)
+        project.configure(task)
 
         then:
-        File eclipseFile = tmpDir.file("eclipse.xml")
-        project.toXml(eclipseFile)
-        StringWriter stringWriterFileXml = new StringWriter()
-        new XmlNodePrinter(new PrintWriter(stringWriterFileXml)).print(new XmlParser().parse(eclipseFile))
-        StringWriter stringWriterWrittenXml = new StringWriter()
-        new XmlNodePrinter(new PrintWriter(stringWriterWrittenXml)).print(new XmlParser().parse(getToXmlReader(project)))
-        StringWriter stringWriterInternalXml = new StringWriter()
-        new XmlNodePrinter(new PrintWriter(stringWriterInternalXml)).print(project.xml)
-
-        stringWriterWrittenXml.toString() == stringWriterInternalXml.toString()
-        stringWriterWrittenXml.toString() == stringWriterFileXml.toString()
+        project.name == task.projectName
+        project.comment == task.comment
+        project.referencedProjects == task.referencedProjects + CUSTOM_REFERENCED_PROJECTS
+        project.buildCommands == CUSTOM_BUILD_COMMANDS + task.buildCommands
+        project.natures == CUSTOM_NATURES + task.natures
+        project.links == task.links + CUSTOM_LINKS
     }
 
-    def toXml_shouldContainCustomValues() {
-        def constructorName = 'constructorName'
-        def constructorComment = 'constructorComment'
-        def constructorReferencedProjects = ['constructorRefProject'] as LinkedHashSet
-
+    def loadDefaults() {
         when:
-        Project project = createProject(name: constructorName, comment: constructorComment,
-                referencedProjects: constructorReferencedProjects, reader: customProjectReader)
-        def projectFromXml = createProject(reader: getToXmlReader(project))
+        project.loadDefaults()
 
         then:
-        project == projectFromXml
+        project.name == ""
+        project.comment == ""
+        project.referencedProjects == [] as Set
+        project.buildCommands == []
+        project.natures == []
+        project.links == [] as Set
     }
 
-    def beforeConfigured() {
-        def constructorNatures = ['constructorNature'] 
-        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
-        beforeConfiguredActions.add("execute") { Project project ->
-            project.natures.clear()
-        }
-
-        when:
-        Project project = createProject(natures: constructorNatures, reader: customProjectReader, beforeConfiguredActions: beforeConfiguredActions)
-
-        then:
-        createProject(reader: getToXmlReader(project)).natures == constructorNatures
-    }
-
-    def whenConfigured() {
-        def constructorNature = 'constructorNature'
-        def configureActionNature = 'configureNature'
-
-        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
-        whenConfiguredActions.add("execute") { Project project ->
-            assert project.natures.contains(CUSTOM_NATURES[0])
-            assert project.natures.contains(constructorNature)
-            project.natures.add(configureActionNature)
-        }
-
-        when:
-        Project project = createProject(natures: [constructorNature], reader: customProjectReader,
-                whenConfiguredActions: whenConfiguredActions)
-
-        then:
-        createProject(reader: getToXmlReader(project)).natures == CUSTOM_NATURES + ([constructorNature, configureActionNature] as LinkedHashSet)
-    }
-
-    def withXml() {
-        XmlTransformer withXmlActions = new XmlTransformer()
-        Project project = createProject(reader: customProjectReader, withXmlActions: withXmlActions)
+    def toXml_shouldContainCustomValues() {
+        EclipseProject task = HelperUtil.createTask(EclipseProject)
+        task.projectName = 'constructorName'
+        task.comment = 'constructorComment'
+        task.referencedProjects = ['constructorRefProject'] as LinkedHashSet
 
         when:
-        def newName
-        withXmlActions.addAction { XmlProvider provider ->
-            def xml = provider.asNode()
-            newName = xml.name.text() + 'x'
-            xml.remove(xml.name)
-            xml.appendNode('name', newName)
-        }
+        project.load(customProjectReader)
+        project.configure(task)
+        def xml = getToXmlReader()
+        def other = new Project(new XmlTransformer())
+        other.load(xml)
 
         then:
-        new XmlParser().parse(getToXmlReader(project)).name.text() == newName
-    }
-
-    private InputStreamReader getCustomProjectReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('customProject.xml'))
+        project == other
     }
 
-    private Project createProject(Map customArgs) {
-        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
-        XmlTransformer transformer = new XmlTransformer()
-        Map args = [name: null, comment: null, referencedProjects: [] as Set, natures: [], buildCommands: [],
-                links: [] as Set, reader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: transformer] + customArgs
-        EclipseProject eclipseProjectStub = Mock()
-        eclipseProjectStub.getProjectName() >> args.name
-        eclipseProjectStub.getComment() >> args.comment
-        eclipseProjectStub.getReferencedProjects() >> args.referencedProjects
-        eclipseProjectStub.getNatures() >> args.natures
-        eclipseProjectStub.getBuildCommands() >> args.buildCommands
-        eclipseProjectStub.getLinks() >> args.links
-        eclipseProjectStub.getBeforeConfiguredActions() >> args.beforeConfiguredActions
-        eclipseProjectStub.getWhenConfiguredActions() >> args.whenConfiguredActions
-        eclipseProjectStub.getWithXmlActions() >> args.withXmlActions
-        return new Project(eclipseProjectStub, args.reader)
+    private InputStream getCustomProjectReader() {
+        return getClass().getResourceAsStream('customProject.xml')
     }
 
-    private StringReader getToXmlReader(Project project) {
-        StringWriter toXmlText = new StringWriter()
-        project.toXml(toXmlText)
-        return new StringReader(toXmlText.toString())
+    private InputStream getToXmlReader() {
+        ByteArrayOutputStream toXmlText = new ByteArrayOutputStream()
+        project.store(toXmlText)
+        return new ByteArrayInputStream(toXmlText.toByteArray())
     }
 }
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy
index d21884b..490db2c 100644
--- a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy
@@ -16,13 +16,12 @@
 package org.gradle.plugins.eclipse.model;
 
 
-import org.gradle.api.Action
-import org.gradle.listener.ListenerBroadcast
+import org.gradle.listener.ActionBroadcast
+import org.gradle.plugins.eclipse.EclipseWtp
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
-import org.gradle.plugins.eclipse.EclipseWtp
 
 /**
  * @author Hans Dockter
@@ -30,7 +29,6 @@ import org.gradle.plugins.eclipse.EclipseWtp
 
 public class WtpTest extends Specification {
     private static final List CUSTOM_WB_MODULE_ENTRIES = [
-            new WbProperty('context-root', 'recu'),
             new WbDependentModule('/WEB-INF/lib', "module:/classpath/myapp-1.0.0.jar"),
             new WbResource("/WEB-INF/classes", "src/main/java")]
     private static final List CUSTOM_FACETS = [new Facet('jst.web', '2.4'), new Facet('jst.java', '1.4')]
@@ -43,21 +41,24 @@ public class WtpTest extends Specification {
 
         expect:
         wtp.deployName == 'recu'
+        wtp.contextPath == 'recu'
         wtp.wbModuleEntries == CUSTOM_WB_MODULE_ENTRIES
         wtp.facets == CUSTOM_FACETS
     }
 
     def initWithReaderAndValues_shouldBeMerged() {
         def constructorDeployName = 'build'
+        def constructorContextPath = 'context'
         def constructorWbModuleEntries = [createSomeWbModuleEntry()]
         def constructorFacets = [createSomeFacet()]
 
         Wtp wtp = createWtp(wbModuleEntries: constructorWbModuleEntries + [CUSTOM_WB_MODULE_ENTRIES[0]], facets: constructorFacets + [CUSTOM_FACETS[0]],
-                deployName: constructorDeployName, componentReader: customComponentReader, facetReader: customFacetReader)
+                deployName: constructorDeployName, contextPath: constructorContextPath, componentReader: customComponentReader, facetReader: customFacetReader)
 
         expect:
         wtp.wbModuleEntries == CUSTOM_WB_MODULE_ENTRIES + constructorWbModuleEntries
         wtp.deployName == constructorDeployName
+        wtp.contextPath == constructorContextPath
         wtp.facets == CUSTOM_FACETS + constructorFacets
     }
 
@@ -104,11 +105,13 @@ public class WtpTest extends Specification {
 
     def toXml_shouldContainCustomValues() {
         def constructorDeployName = 'build'
+        def constructorContextPath = 'contextPath'
         def constructorWbModuleEntries = [createSomeWbModuleEntry()]
         def constructorFacets = [createSomeFacet()]
 
         Wtp wtp = createWtp(wbModuleEntries: constructorWbModuleEntries, facets: constructorFacets,
-                deployName: constructorDeployName, componentReader: customComponentReader, facetReader: customFacetReader)
+                deployName: constructorDeployName, contextPath: constructorContextPath,
+                componentReader: customComponentReader, facetReader: customFacetReader)
         def (componentReader, facetReader) = getToXmlReaders(wtp)
 
         when:
@@ -121,8 +124,8 @@ public class WtpTest extends Specification {
     def beforeConfigured() {
         def constructorWbModuleEntries = [createSomeWbModuleEntry()]
         def constructorFacets = [createSomeFacet()]
-        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
-        beforeConfiguredActions.add("execute") { Wtp wtp ->
+        ActionBroadcast beforeConfiguredActions = new ActionBroadcast()
+        beforeConfiguredActions.add { Wtp wtp ->
             wtp.wbModuleEntries.clear()
             wtp.facets.clear()
         }
@@ -145,8 +148,8 @@ public class WtpTest extends Specification {
         def configureActionWbModuleEntry = createSomeWbModuleEntry()
         configureActionWbModuleEntry.name = configureActionWbModuleEntry.name + 'Other'
 
-        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
-        whenConfiguredActions.add("execute") { Wtp wtp ->
+        ActionBroadcast whenConfiguredActions = new ActionBroadcast()
+        whenConfiguredActions.add { Wtp wtp ->
             assert wtp.wbModuleEntries.contains(CUSTOM_WB_MODULE_ENTRIES[0])
             assert wtp.wbModuleEntries.contains(constructorWbModuleEntry)
             wtp.wbModuleEntries.add(configureActionWbModuleEntry)
@@ -162,12 +165,12 @@ public class WtpTest extends Specification {
     }
 
     def withXml() {
-        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        ActionBroadcast withXmlActions = new ActionBroadcast()
         Wtp wtp = createWtp(componentReader: customComponentReader,
                 facetReader: customFacetReader, withXmlActions: withXmlActions)
 
         when:
-        withXmlActions.add("execute") { xmls ->
+        withXmlActions.add { xmls ->
             xmls['org.eclipse.wst.commons.component'].'wb-module'.property.find { it. at name == 'context-root' }. at value = 'newValue'
             xmls['org.eclipse.wst.commons.project.facet.core'].installed.find { it. at facet == 'jst.java' }. at version = '-5x'
         }
@@ -195,7 +198,7 @@ public class WtpTest extends Specification {
     }
 
     private Wtp createWtp(Map customArgs) {
-        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        ActionBroadcast dummyBroadcast = new ActionBroadcast()
         Map args = [wbModuleEntries: [], facets: [], deployName: null, defaultOutput: null, componentReader: null,
                 facetReader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: dummyBroadcast] + customArgs
         EclipseWtp eclipseWtpStub = Mock()
@@ -203,6 +206,7 @@ public class WtpTest extends Specification {
         eclipseWtpStub.getWhenConfiguredActions() >> args.whenConfiguredActions
         eclipseWtpStub.getWithXmlActions() >> args.withXmlActions
         eclipseWtpStub.getDeployName() >> args.deployName
+        eclipseWtpStub.getContextPath() >> args.contextPath
         eclipseWtpStub.getFacets() >> args.facets
         return new Wtp(eclipseWtpStub, args.wbModuleEntries, args.componentReader, args.facetReader)
     }
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy
index 885a350..8a3a078 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy
@@ -15,26 +15,26 @@
  */
 package org.gradle.plugins.idea
 
-import org.gradle.api.Action
-import org.gradle.api.artifacts.maven.XmlProvider
-import org.gradle.api.internal.ConventionTask
-import org.gradle.api.internal.XmlTransformer
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.api.specs.Specs
-import org.gradle.listener.ListenerBroadcast
 import org.gradle.plugins.idea.model.ModuleLibrary
 import org.gradle.plugins.idea.model.Path
 import org.gradle.plugins.idea.model.PathFactory
-import org.gradle.plugins.idea.model.VariableReplacement
-import org.gradle.api.artifacts.*
 import org.gradle.api.tasks.*
+import org.gradle.plugins.idea.model.Module
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.artifacts.ExternalDependency
+import org.gradle.api.artifacts.ResolvedConfiguration
+import org.gradle.api.artifacts.SelfResolvingDependency
+import org.gradle.api.artifacts.ResolvedDependency
+import org.gradle.api.artifacts.Dependency
 
 /**
  * Generates an IDEA module file.
  *
  * @author Hans Dockter
  */
-public class IdeaModule extends ConventionTask {
+public class IdeaModule extends XmlGeneratorTask<Module> {
     /**
      * The content root directory of the module. Must not be null.
      */
@@ -42,12 +42,6 @@ public class IdeaModule extends ConventionTask {
     File moduleDir
 
     /**
-     * The iml file. Used to look for existing files as well as the target for generation. Must not be null. 
-     */
-    @OutputFile
-    File outputFile
-
-    /**
      * The dirs containing the production sources. Must not be null.
      */
     @InputFiles
@@ -98,18 +92,10 @@ public class IdeaModule extends ConventionTask {
     boolean downloadJavadoc = false
 
     /**
-     * If this variable is set, dependencies in the existing iml file will be parsed for this variable.
-     * If they use it, it will be replaced with a path that has the $MODULE_DIR$ variable as a root and
-     * then a relative path to  {@link #gradleCacheHome} . That way Gradle can recognize equal dependencies.
-     */
-    @Input @Optional
-    String gradleCacheVariable
-
-    /**
-     * This variable is used in conjunction with the {@link #gradleCacheVariable}.
+     * The variables to be used for replacing absolute paths in the iml entries. For example, you might add a
+     * GRADLE_USER_HOME variable to point to the Gradle user home dir.
      */
-    @InputFiles @Optional
-    File gradleCacheHome
+    Map<String, File> variables = [:]
 
     /**
      * The keys of this map are the Intellij scopes. Each key points to another map that has two keys, plus and minus.
@@ -118,20 +104,13 @@ public class IdeaModule extends ConventionTask {
      */
     Map scopes = [:]
 
-    private ListenerBroadcast<Action> beforeConfiguredActions = new ListenerBroadcast<Action>(Action.class);
-    private ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
-    private XmlTransformer withXmlActions = new XmlTransformer();
-
-    def IdeaModule() {
-        outputs.upToDateWhen { false }
+    @Override protected Module create() {
+        return new Module(xmlTransformer, pathFactory)
     }
 
-    @TaskAction
-    void updateXML() {
-        Reader xmlreader = getOutputFile().exists() ? new FileReader(getOutputFile()) : null;
-        org.gradle.plugins.idea.model.Module module = new org.gradle.plugins.idea.model.Module(getContentPath(), getSourcePaths(), getTestSourcePaths(), getExcludePaths(), getOutputPath(), getTestOutputPath(),
-                getDependencies(), getVariableReplacement(), javaVersion, xmlreader, beforeConfiguredActions.source, whenConfiguredActions.source, withXmlActions)
-        getOutputFile().withWriter {Writer writer -> module.toXml(writer)}
+    @Override protected void configure(Module module) {
+        module.configure(getContentPath(), getSourcePaths(), getTestSourcePaths(), getExcludePaths(), getOutputPath(), getTestOutputPath(),
+                getDependencies(), javaVersion)
     }
 
     protected Path getContentPath() {
@@ -165,14 +144,6 @@ public class IdeaModule extends ConventionTask {
         }
     }
 
-    protected getVariableReplacement() {
-        if (getGradleCacheHome() && getGradleCacheVariable()) {
-            String replacer = org.gradle.plugins.idea.model.Path.getRelativePath(getOutputFile().parentFile, '$MODULE_DIR$', getGradleCacheHome())
-            return new VariableReplacement(replacer: replacer, replacable: '$' + getGradleCacheVariable() + '$')
-        }
-        return VariableReplacement.NO_REPLACEMENT
-    }
-
     protected Set getModules(String scope) {
         if (scopes[scope]) {
             return getScopeDependencies(scopes[scope], { it instanceof ProjectDependency}).collect { ProjectDependency projectDependency ->
@@ -293,38 +264,15 @@ public class IdeaModule extends ConventionTask {
     }
 
     protected Path getPath(File file) {
-        PathFactory factory = new PathFactory()
-        factory.addPathVariable('MODULE_DIR', getOutputFile().parentFile)
-        return factory.path(file)
+        return pathFactory.path(file)
     }
 
-    /**
-     * Adds a closure to be called when the IML XML has been created. The XML is passed to the closure as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The closure can modify the XML.
-     *
-     * @param closure The closure to execute when the IML XML has been created.
-     * @return this
-     */
-    void withXml(Closure closure) {
-        withXmlActions.addAction(closure)
-    }
-
-    /**
-     * Adds an action to be called when the IML XML has been created. The XML is passed to the action as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The action can modify the XML.
-     *
-     * @param closure The action to execute when the IML XML has been created.
-     * @return this
-     */
-    void withXml(Action<XmlProvider> action) {
-        withXmlActions.addAction(action)
-    }
-
-    void beforeConfigured(Closure closure) {
-        beforeConfiguredActions.add("execute", closure);
-    }
-
-    void whenConfigured(Closure closure) {
-        whenConfiguredActions.add("execute", closure);
+    protected PathFactory getPathFactory() {
+        PathFactory factory = new PathFactory()
+        factory.addPathVariable('MODULE_DIR', getOutputFile().parentFile)
+        variables.each { key, value ->
+            factory.addPathVariable(key, value)
+        }
+        return factory
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy
index 4590523..5719e53 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy
@@ -17,8 +17,8 @@ package org.gradle.plugins.idea;
 
 
 import org.gradle.api.JavaVersion
-import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.internal.plugins.IdePlugin
 import org.gradle.api.plugins.JavaPlugin
 
 /**
@@ -30,15 +30,14 @@ import org.gradle.api.plugins.JavaPlugin
  * If the java plugin is or has been added to a project where this plugin is applied to, the IdeaModule task gets some
  * Java specific configuration.
  */
-class IdeaPlugin implements Plugin<Project> {
-    void apply(Project project) {
-        project.apply plugin: 'base' // We apply the base plugin to have the clean<taskname> rule
-        def task = project.task('cleanIdea')
-        task.description = 'Cleans IDEA project files (IML, IPR)'
-        task.group = 'IDE'
-        task = project.task('idea')
-        task.description = 'Generates IDEA project files (IML, IPR, IWS)'
-        task.group = 'IDE'
+class IdeaPlugin extends IdePlugin {
+    @Override protected String getLifecycleTaskName() {
+        return 'idea'
+    }
+
+    @Override protected void onApply(Project project) {
+        lifecycleTask.description = 'Generates IDEA project files (IML, IPR, IWS)'
+        cleanTask.description = 'Cleans IDEA project files (IML, IPR)'
         configureIdeaWorkspace(project)
         configureIdeaProject(project)
         configureIdeaModule(project)
@@ -47,40 +46,33 @@ class IdeaPlugin implements Plugin<Project> {
 
     private def configureIdeaWorkspace(Project project) {
         if (isRoot(project)) {
-            project.task('ideaWorkspace', description: 'Generates an IDEA workspace file (IWS)', type: IdeaWorkspace) {
+            def task = project.task('ideaWorkspace', description: 'Generates an IDEA workspace file (IWS)', type: IdeaWorkspace) {
                 outputFile = new File(project.projectDir, project.name + ".iws")
             }
-            project.idea.dependsOn 'ideaWorkspace'
-
-            project.cleanIdea.dependsOn "cleanIdeaWorkspace"
+            addWorker(task)
         }
     }
 
     private def configureIdeaModule(Project project) {
-        project.task('ideaModule', description: 'Generates IDEA module files (IML)', type: IdeaModule) {
+        def task = project.task('ideaModule', description: 'Generates IDEA module files (IML)', type: IdeaModule) {
             conventionMapping.outputFile = { new File(project.projectDir, project.name + ".iml") }
             conventionMapping.moduleDir = { project.projectDir }
             conventionMapping.sourceDirs = { [] as Set }
             conventionMapping.excludeDirs = { [project.buildDir, project.file('.gradle')] as Set }
             conventionMapping.testSourceDirs = { [] as Set }
-            conventionMapping.gradleCacheHome = { new File(project.gradle.gradleUserHomeDir, '/cache') }
         }
-        project.idea.dependsOn 'ideaModule'
-
-        project.cleanIdea.dependsOn "cleanIdeaModule"
+        addWorker(task)
     }
 
     private def configureIdeaProject(Project project) {
         if (isRoot(project)) {
-            project.task('ideaProject', description: 'Generates IDEA project file (IPR)', type: IdeaProject) {
+            def task = project.task('ideaProject', description: 'Generates IDEA project file (IPR)', type: IdeaProject) {
                 outputFile = new File(project.projectDir, project.name + ".ipr")
                 subprojects = project.rootProject.allprojects
                 javaVersion = JavaVersion.VERSION_1_6.toString()
                 wildcards = ['!?*.java', '!?*.groovy']
             }
-            project.idea.dependsOn 'ideaProject'
-
-            project.cleanIdea.dependsOn "cleanIdeaProject"
+            addWorker(task)
         }
     }
 
@@ -103,7 +95,7 @@ class IdeaPlugin implements Plugin<Project> {
         project.ideaModule {
             conventionMapping.sourceDirs = { project.sourceSets.main.allSource.sourceTrees.srcDirs.flatten() as Set }
             conventionMapping.testSourceDirs = { project.sourceSets.test.allSource.sourceTrees.srcDirs.flatten() as Set }
-            conventionMapping.outputDir = { project.sourceSets.main.classesDir } 
+            conventionMapping.outputDir = { project.sourceSets.main.classesDir }
             conventionMapping.testOutputDir = { project.sourceSets.test.classesDir }
             def configurations = project.configurations
             scopes = [
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy
index 1203921..feb8ff1 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy
@@ -15,15 +15,10 @@
  */
 package org.gradle.plugins.idea
 
-import org.gradle.api.Action
-import org.gradle.api.DefaultTask
-import org.gradle.api.artifacts.maven.XmlProvider
-import org.gradle.api.internal.XmlTransformer
 import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-import org.gradle.listener.ListenerBroadcast
+import org.gradle.api.tasks.XmlGeneratorTask
 import org.gradle.plugins.idea.model.ModulePath
+import org.gradle.plugins.idea.model.PathFactory
 import org.gradle.plugins.idea.model.Project
 
 /**
@@ -31,7 +26,7 @@ import org.gradle.plugins.idea.model.Project
  *
  * @author Hans Dockter
  */
-public class IdeaProject extends DefaultTask {
+public class IdeaProject extends XmlGeneratorTask<Project> {
     /**
      * The subprojects that should be mapped to modules in the ipr file. The subprojects will only be mapped, if the Idea plugin has been
      * applied to them.
@@ -39,12 +34,6 @@ public class IdeaProject extends DefaultTask {
     Set subprojects
 
     /**
-     * The ipr file
-     */
-    @OutputFile
-    File outputFile
-
-    /**
      * The java version used for defining the project sdk.
      */
     @Input
@@ -56,78 +45,24 @@ public class IdeaProject extends DefaultTask {
     @Input
     Set wildcards
 
-    private ListenerBroadcast<Action> beforeConfiguredActions = new ListenerBroadcast<Action>(Action.class);
-    private ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
-    private XmlTransformer withXmlActions = new XmlTransformer();
-
-    def IdeaProject() {
-        outputs.upToDateWhen { false }
+    Project create() {
+        return new Project(xmlTransformer, getPathFactory())
     }
 
-    @TaskAction
-    void updateXML() {
-        Reader xmlreader = outputFile.exists() ? new FileReader(outputFile) : null;
+    void configure(Project ideaProject) {
         Set modules = subprojects.inject(new LinkedHashSet()) { result, subproject ->
             if (subproject.plugins.hasPlugin(IdeaPlugin)) {
                 File imlFile = subproject.ideaModule.outputFile
-                result << new ModulePath(outputFile.parentFile, '$PROJECT_DIR$', imlFile)
+                result << new ModulePath(pathFactory.relativePath('PROJECT_DIR', imlFile))
             }
             result
         }
-        Project ideaProject = new Project(modules, javaVersion, wildcards, xmlreader,
-                beforeConfiguredActions.source, whenConfiguredActions.source, withXmlActions)
-        outputFile.withWriter {Writer writer -> ideaProject.toXml(writer)}
-    }
-
-    /**
-     * Adds a closure to be called when the IPR XML has been created. The XML is passed to the closure as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The closure can modify the XML.
-     *
-     * @param closure The closure to execute when the IPR XML has been created.
-     * @return this
-     */
-    IdeaProject withXml(Closure closure) {
-        withXmlActions.addAction(closure)
-        return this;
-    }
-
-    /**
-     * Adds an action to be called when the IPR XML has been created. The XML is passed to the action as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The action can modify the XML.
-     *
-     * @param closure The action to execute when the IPR XML has been created.
-     * @return this
-     */
-    IdeaProject withXml(Action<? super XmlProvider> action) {
-        withXmlActions.addAction(action)
-        return this;
+        ideaProject.configure(modules, javaVersion, wildcards)
     }
 
-    /**
-     * Adds a closure to be called after the existing ipr xml or the default xml has been parsed. The information
-     * of this xml is used to populate the domain objects that model the customizable aspects of the ipr file.
-     * The closure is called before the parameter of this task are added to the domain objects. This hook allows you
-     * to do a partial clean for example. You can delete all modules from the existing xml while keeping all the other
-     * parts. The closure gets an instance of {@link org.gradle.plugins.idea.model.Project} which can be modified.
-     *
-     * @param closure The closure to execute when the existing or default ipr xml has been parsed.
-     * @return this
-     */
-    IdeaProject beforeConfigured(Closure closure) {
-        beforeConfiguredActions.add("execute", closure);
-        return this;
-    }
-
-    /**
-     * Adds a closure after the domain objects that model the customizable aspects of the ipr file are fully populated.
-     * Those objects are populated with the content of the existing or default ipr xml and the arguments of this task.
-     * The closure gets an instance of {@link Project} which can be modified.
-     *
-     * @param closure The closure to execute after the {@link org.gradle.plugins.idea.model.Project} object has been fully populated.
-     * @return this
-     */
-    IdeaProject whenConfigured(Closure closure) {
-        whenConfiguredActions.add("execute", closure);
-        return this;
+    PathFactory getPathFactory() {
+        PathFactory factory = new PathFactory()
+        factory.addPathVariable('PROJECT_DIR', outputFile.parentFile)
+        return factory
     }
 }
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy
index 9a90e9c..d863a65 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy
@@ -15,58 +15,19 @@
  */
 package org.gradle.plugins.idea
 
-import org.gradle.api.DefaultTask
-import org.gradle.api.internal.XmlTransformer
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.XmlGeneratorTask
 import org.gradle.plugins.idea.model.Workspace
-import org.gradle.api.artifacts.maven.XmlProvider
-import org.gradle.api.Action
 
 /**
  * Generates an IDEA workspace file.
  *
  * @author Hans Dockter
  */
-public class IdeaWorkspace extends DefaultTask {
-    /**
-     * The iws file. Used to look for existing files as well as the target for generation. Must not be null.
-     */
-    @OutputFile
-    File outputFile
-
-    private XmlTransformer withXmlActions = new XmlTransformer()
-
-    def IdeaWorkspace() {
-        outputs.upToDateWhen { false }
-    }
-
-    @TaskAction
-    void updateXML() {
-        Reader xmlreader = outputFile.exists() ? new FileReader(outputFile) : null;
-        Workspace workspace = new Workspace(xmlreader, withXmlActions)
-        outputFile.withWriter { Writer writer -> workspace.toXml(writer) }
-    }
-
-    /**
-     * Adds a closure to be called when the IWS XML has been created. The XML is passed to the closure as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The closure can modify the XML.
-     *
-     * @param closure The closure to execute when the IWS XML has been created.
-     * @return this
-     */
-    void withXml(Closure closure) {
-        withXmlActions.addAction(closure);
+public class IdeaWorkspace extends XmlGeneratorTask<Workspace> {
+    @Override protected Workspace create() {
+        return new Workspace(xmlTransformer)
     }
 
-    /**
-     * Adds an action to be called when the IWS XML has been created. The XML is passed to the action as a
-     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The action can modify the XML.
-     *
-     * @param closure The action to execute when the IWS XML has been created.
-     * @return this
-     */
-    void withXml(Action<? super XmlProvider> action) {
-        withXmlActions.addAction(action)
+    @Override protected void configure(Workspace object) {
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy
index cd5342e..1e26451 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy
@@ -86,8 +86,8 @@ class Jdk {
         return "Jdk{" +
                 "assertKeyword=" + assertKeyword +
                 ", jdk15=" + jdk15 +
-                ", languageLevel=" + languageLevel +
-                ", projectJdkName='" + projectJdkName + '\'' +
+                ", languageLevel='" + languageLevel +
+                "', projectJdkName='" + projectJdkName + '\'' +
                 '}';
     }
 }
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy
index 73b0ebc..20f81c5 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy
@@ -15,15 +15,15 @@
  */
 package org.gradle.plugins.idea.model
 
-import org.gradle.api.Action
 import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.tasks.generator.XmlPersistableConfigurationObject
 
 /**
  * Represents the customizable elements of an iml (via XML hooks everything of the iml is customizable).
  *
  * @author Hans Dockter
  */
-class Module {
+class Module extends XmlPersistableConfigurationObject {
     static final String INHERITED = "inherited"
 
     /**
@@ -64,44 +64,22 @@ class Module {
 
     String javaVersion
 
-    private Node xml
+    private final PathFactory pathFactory
 
-    private XmlTransformer withXmlActions
-
-    def Module(Path contentPath, Set sourceFolders, Set testSourceFolders, Set excludeFolders, Path outputDir, Path testOutputDir, Set dependencies,
-               VariableReplacement dependencyVariableReplacement, String javaVersion, Reader inputXml,
-               Action<Module> beforeConfiguredAction, Action<Module> whenConfiguredAction,
-               XmlTransformer withXmlActions) {
-        initFromXml(inputXml, dependencyVariableReplacement)
-
-        beforeConfiguredAction.execute(this)
-
-        this.contentPath = contentPath
-        this.sourceFolders.addAll(sourceFolders);
-        this.testSourceFolders.addAll(testSourceFolders);
-        this.excludeFolders.addAll(excludeFolders);
-        if (outputDir) {
-            this.outputDir = outputDir
-        }
-        if (testOutputDir) {
-            this.testOutputDir = testOutputDir;
-        }
-        this.dependencies.addAll(dependencies);
-        if (javaVersion) {
-            this.javaVersion = javaVersion
-        }
-        this.withXmlActions = withXmlActions;
+    def Module(XmlTransformer withXmlActions, PathFactory pathFactory) {
+        super(withXmlActions)
+        this.pathFactory = pathFactory
+    }
 
-        whenConfiguredAction.execute(this)
+    @Override protected String getDefaultResourceName() {
+        return 'defaultModule.xml'
     }
 
-    private def initFromXml(Reader inputXml, VariableReplacement dependencyVariableReplacement) {
-        Reader reader = inputXml ?: new InputStreamReader(getClass().getResourceAsStream('defaultModule.xml'))
-        xml = new XmlParser().parse(reader)
+    @Override protected void load(Node xml) {
         readJdkFromXml()
         readSourceAndExcludeFolderFromXml()
         readOutputDirsFromXml()
-        readDependenciesFromXml(dependencyVariableReplacement)
+        readDependenciesFromXml()
     }
 
     private def readJdkFromXml() {
@@ -116,24 +94,24 @@ class Module {
     private def readOutputDirsFromXml() {
         def outputDirUrl = findOutputDir()?. at url
         def testOutputDirUrl = findTestOutputDir()?. at url
-        this.outputDir = outputDirUrl ? new Path(outputDirUrl) : null
-        this.testOutputDir = testOutputDirUrl ? new Path(testOutputDirUrl) : null
+        this.outputDir = outputDirUrl ? pathFactory.path(outputDirUrl) : null
+        this.testOutputDir = testOutputDirUrl ? pathFactory.path(testOutputDirUrl) : null
     }
 
-    private def readDependenciesFromXml(VariableReplacement dependencyVariableReplacement) {
+    private def readDependenciesFromXml() {
         return findOrderEntries().each { orderEntry ->
             switch (orderEntry. at type) {
                 case "module-library":
                     Set classes = orderEntry.library.CLASSES.root.collect {
-                        new Path(dependencyVariableReplacement.replace(it. at url))
+                        pathFactory.path(it. at url)
                     }
                     Set javadoc = orderEntry.library.JAVADOC.root.collect {
-                        new Path(dependencyVariableReplacement.replace(it. at url))
+                        pathFactory.path(it. at url)
                     }
                     Set sources = orderEntry.library.SOURCES.root.collect {
-                        new Path(dependencyVariableReplacement.replace(it. at url))
+                        pathFactory.path(it. at url)
                     }
-                    Set jarDirectories = orderEntry.library.jarDirectory.collect { new JarDirectory(new Path(it. at url), Boolean.parseBoolean(it. at recursive)) }
+                    Set jarDirectories = orderEntry.library.jarDirectory.collect { new JarDirectory(pathFactory.path(it. at url), Boolean.parseBoolean(it. at recursive)) }
                     def moduleLibrary = new ModuleLibrary(classes, javadoc, sources, jarDirectories, orderEntry. at scope)
                     dependencies.add(moduleLibrary)
                     break
@@ -146,22 +124,35 @@ class Module {
     private def readSourceAndExcludeFolderFromXml() {
         findSourceFolder().each { sourceFolder ->
             if (sourceFolder. at isTestSource == 'false') {
-                this.sourceFolders.add(new Path(sourceFolder. at url))
+                this.sourceFolders.add(pathFactory.path(sourceFolder. at url))
             } else {
-                this.testSourceFolders.add(new Path(sourceFolder. at url))
+                this.testSourceFolders.add(pathFactory.path(sourceFolder. at url))
             }
         }
         findExcludeFolder().each { excludeFolder ->
-            this.excludeFolders.add(new Path(excludeFolder. at url))
+            this.excludeFolders.add(pathFactory.path(excludeFolder. at url))
         }
     }
 
-    /**
-     * Generates the XML for the iml.
-     *
-     * @param writer The writer where the iml xml is generated into.
-     */
-    def toXml(Writer writer) {
+    def configure(Path contentPath, Set sourceFolders, Set testSourceFolders, Set excludeFolders, Path outputDir, Path testOutputDir, Set dependencies,
+                  String javaVersion) {
+        this.contentPath = contentPath
+        this.sourceFolders.addAll(sourceFolders);
+        this.testSourceFolders.addAll(testSourceFolders);
+        this.excludeFolders.addAll(excludeFolders);
+        if (outputDir) {
+            this.outputDir = outputDir
+        }
+        if (testOutputDir) {
+            this.testOutputDir = testOutputDir;
+        }
+        this.dependencies.addAll(dependencies);
+        if (javaVersion) {
+            this.javaVersion = javaVersion
+        }
+    }
+
+    @Override protected void store(Node xml) {
         addJdkToXml()
         setContentURL()
         removeSourceAndExcludeFolderFromXml()
@@ -170,8 +161,6 @@ class Module {
 
         removeDependenciesFromXml()
         addDependenciesToXml()
-
-        withXmlActions.transform(xml, writer)
     }
 
     private def addJdkToXml() {
@@ -326,18 +315,3 @@ class Module {
                 '}';
     }
 }
-
-// todo make this an inner class once codenarc understands groovy inner classes
-public class VariableReplacement {
-    public static final VariableReplacement NO_REPLACEMENT = new VariableReplacement()
-
-    String replacable
-    String replacer
-
-    String replace(String source) {
-        if (replacable && replacer != null) {
-            return source.replace(replacable, replacer)
-        }
-        return source
-    }
-}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy
index bfee4c0..ee7678c 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy
@@ -21,48 +21,45 @@ package org.gradle.plugins.idea.model
  * @author Hans Dockter
  */
 
-class ModulePath extends Path {
+class ModulePath {
     /**
      * The path string of this path.
      */
-    final String path
+    final String filePath
 
-    def ModulePath(File rootDir, String rootDirString, File file) {
-        super(rootDir, rootDirString, file)
-        path = relPath
+    final Path path
+
+    def ModulePath(Path path) {
+        this.path = path
+        filePath = path.relPath
     }
 
-    def ModulePath(String url, String path) {
-        super(url)
-        assert path != null
-        this.path = path;
+    def ModulePath(Path path, String filePath) {
+        this.path = path
+        this.filePath = filePath
     }
 
+    String getUrl() {
+        return path.url
+    }
+    
     boolean equals(o) {
         if (this.is(o)) { return true }
 
-        if (o== null || getClass() != o.class) { return false }
-        if (!super.equals(o)) { return false }
+        if (o == null || getClass() != o.class) { return false }
 
         ModulePath that = (ModulePath) o;
-
-        if (path != that.path) { return false }
-
-        return true;
+        return path == that.path && filePath == that.filePath
     }
 
     int hashCode() {
-        int result = super.hashCode();
-
-        result = 31 * result + path.hashCode();
-        return result;
-    }                                
-
+        return path.hashCode() ^ filePath.hashCode()
+    }
 
     public String toString() {
         return "ModulePath{" +
-                "url='" + url +
-                ", path='" + path + '\'' +
+                "path='" + path +
+                ", filePath='" + filePath + '\'' +
                 '}';
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy
index 9e46c39..436ecb8 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy
@@ -31,28 +31,37 @@ class Path {
      */
     final String relPath
 
+    final String canonicalUrl
+
     def Path(File rootDir, String rootDirString, File file) {
         relPath = getRelativePath(rootDir, rootDirString, file)
         url = relativePathToURI(relPath)
+        canonicalUrl = relativePathToURI(file.absolutePath.replace(File.separator, '/'))
     }
 
     def Path(File file) {
         // IDEA doesn't like the result of file.toURI() so use the absolute path instead
         relPath = file.absolutePath.replace(File.separator, '/')
         url = relativePathToURI(relPath)
+        canonicalUrl = url
     }
 
     def Path(String url) {
+        this(url, url)
+    }
+
+    def Path(String url, String canonicalUrl) {
         this.relPath = null
         this.url = url
+        this.canonicalUrl = canonicalUrl
     }
 
-    public static String getRelativePath(File rootDir, String rootDirString, File file) {
+    private static String getRelativePath(File rootDir, String rootDirString, File file) {
         String relpath = getRelativePath(rootDir, file)
         return relpath != null ? rootDirString + '/' + relpath : file.absolutePath.replace(File.separator, '/')
     }
 
-    private String relativePathToURI(String relpath) {
+    private static String relativePathToURI(String relpath) {
         if (relpath.endsWith('.jar')) {
             return 'jar://' + relpath + '!/';
         } else {
@@ -122,18 +131,19 @@ class Path {
 
         Path path = (Path) o;
 
-        if (url != path.url) { return false }
+        if (canonicalUrl != path.canonicalUrl) { return false }
 
         return true;
     }
 
     int hashCode() {
-        return url.hashCode();
+        return canonicalUrl.hashCode();
     }
 
     public String toString() {
         return "Path{" +
                 "url='" + url + '\'' +
+                ", canonicalUrl='" + canonicalUrl + '\'' +
                 '}';
     }
 }
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/PathFactory.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/PathFactory.groovy
index 4e3d48a..59582f8 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/PathFactory.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/PathFactory.groovy
@@ -17,12 +17,21 @@ package org.gradle.plugins.idea.model
 
 class PathFactory {
     private final List<Map> variables = []
+    private final Map<String, File> varsByName = [:]
 
     void addPathVariable(String name, File dir) {
         variables << [name: "\$${name}\$", prefix: dir.absolutePath + File.separator, dir: dir]
+        varsByName[name] = dir
     }
 
+    /**
+     * Creates a path for the given file.
+     */
     Path path(File file) {
+        createFile(file.canonicalFile)
+    }
+
+    private Path createFile(File file) {
         Map match = null
         for (variable in variables) {
             if (file.absolutePath == variable.dir.absolutePath) {
@@ -35,7 +44,7 @@ class PathFactory {
                 }
             }
         }
-        
+
         if (match) {
             return new Path(match.dir, match.name, file)
         }
@@ -43,7 +52,33 @@ class PathFactory {
         return new Path(file)
     }
 
+    /**
+     * Creates a path relative to the given path variable.
+     */
+    Path relativePath(String pathVar, File file) {
+        return new Path(varsByName[pathVar], "\$${pathVar}\$", file)
+    }
+
+    /**
+     * Creates a path for the given URL.
+     */
     Path path(String url) {
-        return new Path(url)
+        String expandedUrl = url
+        for (variable in variables) {
+            expandedUrl = expandedUrl.replace(variable.name, variable.prefix)
+        }
+        if (expandedUrl.toLowerCase().startsWith('file://')) {
+            expandedUrl = toUrl('file', new File(expandedUrl.substring(7)).canonicalFile)
+        } else if (expandedUrl.toLowerCase().startsWith('jar://')) {
+            def parts = expandedUrl.substring(6).split('!')
+            if (parts.length == 2) {
+                expandedUrl = toUrl('jar', new File(parts[0]).canonicalFile) + '!' + parts[1]
+            }
+        }
+        return new Path(url, expandedUrl)
+    }
+
+    def toUrl(String scheme, File file) {
+        return scheme + '://' + file.absolutePath.replace(File.separator, '/')
     }
 }
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy
index dd743bf..c215da7 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.idea.model
 
-import org.gradle.api.Action
+import org.gradle.api.internal.tasks.generator.XmlPersistableConfigurationObject
 import org.gradle.api.internal.XmlTransformer
 
 /**
@@ -23,7 +23,7 @@ import org.gradle.api.internal.XmlTransformer
  *
  * @author Hans Dockter
  */
-class Project {
+class Project extends XmlPersistableConfigurationObject {
     /**
      * A set of {@link Path} instances pointing to the modules contained in the ipr.
      */
@@ -39,29 +39,25 @@ class Project {
      */
     Jdk jdk
 
-    private Node xml
-    
-    private XmlTransformer withXmlActions
+    private final PathFactory pathFactory
 
-    def Project(Set modulePaths, String javaVersion, Set wildcards, Reader inputXml, Action<Project> beforeConfiguredAction,
-                Action<Project> whenConfiguredAction, XmlTransformer withXmlActions) {
-        initFromXml(inputXml, javaVersion)
+    def Project(XmlTransformer xmlTransformer, PathFactory pathFactory) {
+        super(xmlTransformer)
+        this.pathFactory = pathFactory
+    }
 
-        beforeConfiguredAction.execute(this)
+    def configure(Set modulePaths, String javaVersion, Set wildcards) {
+        if (javaVersion) {
+            jdk = new Jdk(javaVersion)
+        }
 
         this.modulePaths.addAll(modulePaths)
         this.wildcards.addAll(wildcards)
-        this.withXmlActions = withXmlActions
-
-        whenConfiguredAction.execute(this)
     }
 
-    private def initFromXml(Reader inputXml, String javaVersion) {
-        Reader reader = inputXml ?: new InputStreamReader(getClass().getResourceAsStream('defaultProject.xml'))
-        xml = new XmlParser().parse(reader)
-
+    @Override protected void load(Node xml) {
         findModules().module.each { module ->
-            this.modulePaths.add(new ModulePath(module. at fileurl, module. at filepath))
+            this.modulePaths.add(new ModulePath(pathFactory.path(module. at fileurl), module. at filepath))
         }
 
         findWildcardResourcePatterns().entry.each { entry ->
@@ -69,19 +65,19 @@ class Project {
         }
         def jdkValues = findProjectRootManager().attributes()
 
-        if (javaVersion) {
-            jdk = new Jdk(javaVersion)
-        } else {
-            jdk = new Jdk(Boolean.parseBoolean(jdkValues.'assert-keyword'), Boolean.parseBoolean(jdkValues.'jdk-15'),
-                          jdkValues.languageLevel, jdkValues.'project-jdk-name')
-        }
+        jdk = new Jdk(Boolean.parseBoolean(jdkValues.'assert-keyword'), Boolean.parseBoolean(jdkValues.'jdk-15'),
+                jdkValues.languageLevel, jdkValues.'project-jdk-name')
+    }
+
+    @Override protected String getDefaultResourceName() {
+        return "defaultProject.xml"
     }
 
-    def toXml(Writer writer) {
+    @Override protected void store(Node xml) {
         findModules().replaceNode {
             modules {
                 modulePaths.each { ModulePath modulePath ->
-                    module(fileurl: modulePath.url, filepath: modulePath.path)
+                    module(fileurl: modulePath.path.url, filepath: modulePath.filePath)
                 }
             }
         }
@@ -96,8 +92,6 @@ class Project {
         findProjectRootManager().@'assert-jdk-15' = jdk.jdk15
         findProjectRootManager(). at languageLevel = jdk.languageLevel
         findProjectRootManager().@'project-jdk-name' = jdk.projectJdkName
-
-        withXmlActions.transform(xml, writer)
     }
 
     private def findProjectRootManager() {
@@ -116,7 +110,6 @@ class Project {
         moduleManager.modules
     }
 
-
     boolean equals(o) {
         if (this.is(o)) { return true }
 
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy
index d005622..cb27c79 100644
--- a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy
@@ -16,6 +16,7 @@
 package org.gradle.plugins.idea.model
 
 import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.tasks.generator.XmlPersistableConfigurationObject
 
 /**
  * Represents the customizable elements of an ipr (via XML hooks everything of the ipr is customizable).
@@ -23,23 +24,18 @@ import org.gradle.api.internal.XmlTransformer
  * @author Hans Dockter
  */
 
-class Workspace {
-    private Node xml
-
-    private XmlTransformer withXmlActions
-
-    def Workspace(Reader inputXml, XmlTransformer withXmlActions) {
-        initFromXml(inputXml)
+class Workspace extends XmlPersistableConfigurationObject {
+    def Workspace(XmlTransformer withXmlActions) {
+        super(withXmlActions)
+    }
 
-        this.withXmlActions = withXmlActions
+    @Override protected String getDefaultResourceName() {
+        return 'defaultWorkspace.xml'
     }
 
-    private def initFromXml(Reader inputXml) {
-        Reader reader = inputXml ?: new InputStreamReader(getClass().getResourceAsStream('defaultWorkspace.xml'))
-        xml = new XmlParser().parse(reader)
+    @Override protected void load(Node xml) {
     }
 
-    def toXml(Writer writer) {
-        withXmlActions.transform(xml, writer)
+    @Override protected void store(Node xml) {
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy
index 32827d6..0912a8b 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy
@@ -98,7 +98,7 @@ class IdeaPluginTest extends Specification {
         assert ideaModuleTask.sourceDirs == [] as Set
         assert ideaModuleTask.testSourceDirs == [] as Set
         assert ideaModuleTask.excludeDirs == [project.buildDir, project.file('.gradle')] as Set
-        assert ideaModuleTask.gradleCacheHome == new File(project.gradle.gradleUserHomeDir, '/cache')
+        assert ideaModuleTask.variables == [:]
         assertThatCleanIdeaDependsOnDeleteTask(project, project.cleanIdeaModule)
     }
 
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy
index e4b2169..01ad10e 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy
@@ -19,10 +19,13 @@ import spock.lang.Specification
 import org.gradle.util.Matchers
 
 class ModulePathTest extends Specification {
-    def pathsAreEqualWhenTheirUrlAndPathAreEqual() {
+    def pathsAreEqualWhenTheirPathAndFilePathAreEqual() {
+        Path path = new Path('url')
+        String filePath = 'path'
+
         expect:
-        Matchers.strictlyEquals(new ModulePath('url', 'path'), new ModulePath('url', 'path'))
-        new ModulePath('url', 'path') != new ModulePath('url', 'other')
-        new ModulePath('url', 'path') != new ModulePath('other', 'path')
+        Matchers.strictlyEquals(new ModulePath(path, filePath), new ModulePath(path, filePath))
+        new ModulePath(path, filePath) != new ModulePath(path, 'other')
+        new ModulePath(path, filePath) != new ModulePath(new Path('other'), filePath)
     }
 }
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy
index f7a4823..05951dd 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy
@@ -15,8 +15,6 @@
  */
 package org.gradle.plugins.idea.model
 
-import org.gradle.api.Action
-import org.gradle.api.artifacts.maven.XmlProvider
 import org.gradle.api.internal.XmlTransformer
 import spock.lang.Specification
 
@@ -24,195 +22,97 @@ import spock.lang.Specification
  * @author Hans Dockter
  */
 class ModuleTest extends Specification {
-    def static final CUSTOM_SOURCE_FOLDERS = [new Path('file://$MODULE_DIR$/src')] as LinkedHashSet
-    def static final CUSTOM_TEST_SOURCE_FOLDERS = [new Path('file://$MODULE_DIR$/srcTest')] as LinkedHashSet
-    def static final CUSTOM_EXCLUDE_FOLDERS = [new Path('file://$MODULE_DIR$/target')] as LinkedHashSet
-    def customDependencies = [
-            new ModuleLibrary([new Path('file://$MODULE_DIR$/gradle/lib')] as Set,
-                    [new Path('file://$MODULE_DIR$/gradle/javadoc')] as Set, [new Path('file://$MODULE_DIR$/gradle/src')] as Set,
+    final PathFactory pathFactory = new PathFactory()
+    final XmlTransformer xmlTransformer = new XmlTransformer()
+    final customSourceFolders = [path('file://$MODULE_DIR$/src')] as LinkedHashSet
+    final customTestSourceFolders = [path('file://$MODULE_DIR$/srcTest')] as LinkedHashSet
+    final customExcludeFolders = [path('file://$MODULE_DIR$/target')] as LinkedHashSet
+    final customDependencies = [
+            new ModuleLibrary([path('file://$MODULE_DIR$/gradle/lib')] as Set,
+                    [path('file://$MODULE_DIR$/gradle/javadoc')] as Set, [path('file://$MODULE_DIR$/gradle/src')] as Set,
                     [] as Set, null),
-            new ModuleLibrary([new Path('file://$MODULE_DIR$/ant/lib'), new Path('jar://$GRADLE_CACHE$/gradle.jar!/')] as Set, [] as Set, [] as Set,
-                    [new JarDirectory(new Path('file://$MODULE_DIR$/ant/lib'), false)] as Set, "RUNTIME"),
+            new ModuleLibrary([path('file://$MODULE_DIR$/ant/lib'), path('jar://$GRADLE_CACHE$/gradle.jar!/')] as Set, [] as Set, [] as Set,
+                    [new JarDirectory(path('file://$MODULE_DIR$/ant/lib'), false)] as Set, "RUNTIME"),
             new ModuleDependency('someModule', null)]
 
-    Module module
+    Module module = new Module(xmlTransformer, pathFactory)
 
-//    def setup() {
-//        customDependencies.each { println it }
-//    }
-
-    def initWithReader() {
-        module = createModule(reader: customModuleReader)
+    def loadFromReader() {
+        when:
+        module.load(customModuleReader)
 
-        expect:
+        then:
         module.javaVersion == "1.6"
-        module.sourceFolders == CUSTOM_SOURCE_FOLDERS
-        module.testSourceFolders == CUSTOM_TEST_SOURCE_FOLDERS
-        module.excludeFolders == CUSTOM_EXCLUDE_FOLDERS
-        module.outputDir == new Path('file://$MODULE_DIR$/out')
-        module.testOutputDir == new Path('file://$MODULE_DIR$/outTest')
+        module.sourceFolders == customSourceFolders
+        module.testSourceFolders == customTestSourceFolders
+        module.excludeFolders == customExcludeFolders
+        module.outputDir == path('file://$MODULE_DIR$/out')
+        module.testOutputDir == path('file://$MODULE_DIR$/outTest')
         (module.dependencies as List) == customDependencies
     }
 
-    def initWithReaderAndDependencyVars() {
-        def replacable = '$GRADLE_CACHE$'
-        def replacer = '$MODULE_DIR$/../'
-        module = createModule(dependencyVariableReplacement: new VariableReplacement(replacable: replacable, replacer: replacer), reader: customModuleReader)
-
-        expect:
-        module.sourceFolders == CUSTOM_SOURCE_FOLDERS
-        module.testSourceFolders == CUSTOM_TEST_SOURCE_FOLDERS
-        module.excludeFolders == CUSTOM_EXCLUDE_FOLDERS
-        module.outputDir == new Path('file://$MODULE_DIR$/out')
-        module.testOutputDir == new Path('file://$MODULE_DIR$/outTest')
-        def expectedDependencies = customDependencies.collect { dependency ->
-            if (dependency instanceof ModuleLibrary) {
-                dependency.classes = dependency.classes.collect { Path path ->
-                    Path newPath = path
-                    if (path.url.contains(replacable)) {
-                        newPath = new Path(path.url.replace(replacable, replacer))
-                    }
-                    newPath
-                }
-            }
-            dependency
-        }
-        module.dependencies == (expectedDependencies as Set)
-    }
-
-    def initWithReaderAndValues_shouldBeMerged() {
-        def constructorSourceFolders = [new Path('a')] as Set
-        def constructorTestSourceFolders = [new Path('b')] as Set
-        def constructorExcludeFolders = [new Path('c')] as Set
-        def constructorOutputDir = new Path('someOut')
+    def configureMergesValues() {
+        def constructorSourceFolders = [path('a')] as Set
+        def constructorTestSourceFolders = [path('b')] as Set
+        def constructorExcludeFolders = [path('c')] as Set
+        def constructorOutputDir = path('someOut')
         def constructorJavaVersion = '1.6'
-        def constructorTestOutputDir = new Path('someTestOut')
+        def constructorTestOutputDir = path('someTestOut')
         def constructorModuleDependencies = [
                 customDependencies[0],
-                new ModuleLibrary([new Path('x')], [], [], [new JarDirectory(new Path('y'), false)], null)] as LinkedHashSet
-        module = createModule(sourceFolders: constructorSourceFolders, testSourceFolders: constructorTestSourceFolders,
-                excludeFolders: constructorExcludeFolders, outputDir: constructorOutputDir, testOutputDir: constructorTestOutputDir,
-                moduleLibraries: constructorModuleDependencies, javaVersion: constructorJavaVersion, reader: customModuleReader)
-
-        expect:
-        module.sourceFolders == CUSTOM_SOURCE_FOLDERS + constructorSourceFolders
-        module.testSourceFolders == CUSTOM_TEST_SOURCE_FOLDERS + constructorTestSourceFolders
-        module.excludeFolders == CUSTOM_EXCLUDE_FOLDERS + constructorExcludeFolders
+                new ModuleLibrary([path('x')], [], [], [new JarDirectory(path('y'), false)], null)] as LinkedHashSet
+
+        when:
+        module.load(customModuleReader)
+        module.configure(null, constructorSourceFolders, constructorTestSourceFolders, constructorExcludeFolders, constructorOutputDir, constructorTestOutputDir, constructorModuleDependencies, constructorJavaVersion)
+
+        then:
+        module.sourceFolders == customSourceFolders + constructorSourceFolders
+        module.testSourceFolders == customTestSourceFolders + constructorTestSourceFolders
+        module.excludeFolders == customExcludeFolders + constructorExcludeFolders
         module.outputDir == constructorOutputDir
         module.testOutputDir == constructorTestOutputDir
         module.javaVersion == constructorJavaVersion
         module.dependencies as LinkedHashSet == ((customDependencies as LinkedHashSet) + constructorModuleDependencies as LinkedHashSet) as LinkedHashSet
     }
 
-    def initWithNullReader_shouldUseDefaultValuesAndMerge() {
-        def constructorSourceFolders = [new Path('a')] as Set
-        module = createModule(sourceFolders: constructorSourceFolders)
-
-        expect:
-        module.javaVersion == Module.INHERITED
-        module.sourceFolders == constructorSourceFolders
-        module.dependencies.size() == 0
-    }
-
-    def initWithNullReader_shouldUseDefaultSkeleton() {
+    def loadDefaults() {
         when:
-        module = createModule([:])
+        module.loadDefaults()
 
         then:
-        new XmlParser().parse(toXmlReader).toString() == module.xml.toString()
+        module.javaVersion == Module.INHERITED
+        module.sourceFolders == [] as Set
+        module.dependencies.size() == 0
     }
 
-    def toXml_shouldContainCustomValues() {
+    def generatedXmlShouldContainCustomValues() {
         def constructorSourceFolders = [new Path('a')] as Set
         def constructorOutputDir = new Path('someOut')
         def constructorTestOutputDir = new Path('someTestOut')
 
         when:
-        this.module = createModule(javaVersion: '1.6', sourceFolders: constructorSourceFolders, reader: defaultModuleReader,
-                outputDir: constructorOutputDir, testOutputDir: constructorTestOutputDir)
-        def module = createModule(reader: toXmlReader)
-
-        then:
-        this.module == module
-    }
-
-    def toXml_shouldContainSkeleton() {
-        when:
-        module = createModule(reader: customModuleReader)
-
-        then:
-        new XmlParser().parse(toXmlReader).toString() == module.xml.toString()
-    }
-
-    def beforeConfigured() {
-        def constructorSourceFolders = [new Path('a')] as Set
-        Action beforeConfiguredActions = { Module module ->
-            module.sourceFolders.clear()
-        } as Action
-
-        when:
-        module = createModule(sourceFolders: constructorSourceFolders, reader: customModuleReader, beforeConfiguredActions: beforeConfiguredActions)
-
-        then:
-        createModule(reader: toXmlReader).sourceFolders == constructorSourceFolders
-    }
-
-    def whenConfigured() {
-        def constructorSourceFolder = new Path('a')
-        def configureActionSourceFolder = new Path("c")
-
-        Action whenConfiguredActions = { Module module ->
-            assert module.sourceFolders.contains((CUSTOM_SOURCE_FOLDERS as List)[0])
-            assert module.sourceFolders.contains(constructorSourceFolder)
-            module.sourceFolders.add(configureActionSourceFolder)
-        } as Action
-
-        when:
-        module = createModule(sourceFolders: [constructorSourceFolder] as Set, reader: customModuleReader,
-                whenConfiguredActions: whenConfiguredActions)
-
-        then:
-        createModule(reader: toXmlReader).sourceFolders == CUSTOM_SOURCE_FOLDERS + ([constructorSourceFolder, configureActionSourceFolder] as LinkedHashSet)
-    }
-
-    private StringReader getToXmlReader() {
-        StringWriter toXmlText = new StringWriter()
-        module.toXml(toXmlText)
-        return new StringReader(toXmlText.toString())
-    }
-
-    def withXml() {
-        XmlTransformer withXmlActions = new XmlTransformer()
-        module = createModule(reader: customModuleReader, withXmlActions: withXmlActions)
-
-        when:
-        def modifiedVersion
-        withXmlActions.addAction { XmlProvider provider ->
-            def xml = provider.asNode()
-            xml. at version += 'x'
-            modifiedVersion = xml. at version
-        }
+        module.loadDefaults()
+        module.configure(null, constructorSourceFolders, [] as Set, [] as Set, constructorOutputDir, constructorTestOutputDir, [] as Set, null)
+        def xml = toXmlReader
+        def newModule = new Module(xmlTransformer, pathFactory)
+        newModule.load(xml)
 
         then:
-        new XmlParser().parse(toXmlReader). at version == modifiedVersion
+        this.module == newModule
     }
 
-    private InputStreamReader getCustomModuleReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('customModule.xml'))
+    private InputStream getToXmlReader() {
+        ByteArrayOutputStream toXmlText = new ByteArrayOutputStream()
+        module.store(toXmlText)
+        return new ByteArrayInputStream(toXmlText.toByteArray())
     }
 
-    private InputStreamReader getDefaultModuleReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('defaultModule.xml'))
+    private InputStream getCustomModuleReader() {
+        return getClass().getResourceAsStream('customModule.xml')
     }
 
-    private Module createModule(Map customArgs) {
-        Action dummyBroadcast = Mock()
-        XmlTransformer xmlTransformer = new XmlTransformer()
-        Map args = [contentPath: null, sourceFolders: [] as Set, testSourceFolders: [] as Set, excludeFolders: [] as Set, outputDir: null, testOutputDir: null,
-                moduleLibraries: [] as Set, dependencyVariableReplacement: VariableReplacement.NO_REPLACEMENT, javaVersion: null,
-                reader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: xmlTransformer] + customArgs
-        return new Module(args.contentPath, args.sourceFolders, args.testSourceFolders, args.excludeFolders, args.outputDir, args.testOutputDir,
-                args.moduleLibraries, args.dependencyVariableReplacement, args.javaVersion, args.reader,
-                args.beforeConfiguredActions, args.whenConfiguredActions, args.withXmlActions)
+    private Path path(String url) {
+        pathFactory.path(url)
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathFactoryTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathFactoryTest.groovy
index e0588bf..4adcc59 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathFactoryTest.groovy
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathFactoryTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.plugins.idea.model
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.util.TestFile
 
 class PathFactoryTest extends Specification {
     @Rule TemporaryFolder tmpDir = new TemporaryFolder()
@@ -34,7 +35,7 @@ class PathFactoryTest extends Specification {
     def createsPathForAFileNotUnderARootDir() {
         factory.addPathVariable('ROOT_DIR', tmpDir.dir)
         def file = tmpDir.dir.parentFile.file('a')
-        def relpath = file.absolutePath.replace(File.separator, '/')
+        def relpath = relpath(file)
 
         expect:
         def path = factory.path(file)
@@ -62,9 +63,108 @@ class PathFactoryTest extends Specification {
         path.url == 'file://$SUB_DIR$/'
     }
 
-    def createsPathForAUrl() {
+    def createsPathForAJarFile() {
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+
+        expect:
+        def path = factory.path(tmpDir.file('a.jar'))
+        path.url == 'jar://$ROOT_DIR$/a.jar!/'
+    }
+
+    def createsRelativePath() {
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+
+        expect:
+        def path = factory.relativePath('ROOT_DIR', tmpDir.file('a/b'))
+        path.url == 'file://$ROOT_DIR$/a/b'
+
+        path = factory.relativePath('ROOT_DIR', tmpDir.dir.parentFile.parentFile.file('a/b'))
+        path.url == 'file://$ROOT_DIR$/../../a/b'
+    }
+    
+    def createsPathForAFileUrl() {
         expect:
         def path = factory.path('file://a/b/c')
         path.url == 'file://a/b/c'
     }
+
+    def createsPathForAJarUrl() {
+        expect:
+        def path = factory.path('jar://a/b/c.jar!/some/entry')
+        path.url == 'jar://a/b/c.jar!/some/entry'
+    }
+
+    def createsPathForAUrlWithUnknownScheme() {
+        expect:
+        def path = factory.path('other:abc')
+        path.url == 'other:abc'
+    }
+
+    def createsPathForAUrlWithPathVariables() {
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+
+        expect:
+        def path = factory.path('file://$ROOT_DIR$/c')
+        path.url == 'file://$ROOT_DIR$/c'
+    }
+
+    def filePathsAreEqualWhenTheyPointToTheSameFile() {
+        TestFile subDir = tmpDir.file('sub')
+        TestFile childFile = tmpDir.file('sub/a/b')
+
+        factory.addPathVariable('SUB_DIR', subDir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+
+        expect:
+
+        // Using files
+        factory.path(subDir) == factory.path(subDir)
+        factory.path(childFile) == factory.path(childFile)
+        factory.path(childFile) != factory.path(subDir)
+
+        // Using normalised absolute urls
+        factory.path(subDir) == factory.path("file://${relpath(subDir)}")
+        factory.path(subDir) == factory.path("file://${relpath(childFile)}/../..")
+        factory.path("file://${relpath(subDir)}") != factory.path("file://${relpath(childFile)}")
+
+        // Using absolute paths
+        factory.path(subDir) == factory.path("file://${subDir.absolutePath}")
+
+        // Using replacement variables
+        factory.path(childFile) == factory.path('file://$SUB_DIR$/a/b')
+        factory.path(childFile) == factory.path('file://$SUB_DIR$/c/../a/b')
+        factory.path('file://$ROOT_DIR$/sub') == factory.path('file://$SUB_DIR$')
+        factory.path('file://$ROOT_DIR$') != factory.path('file://$SUB_DIR$')
+    }
+
+    def filePathsAreEqualWhenTheyPointToTheSameEntryInTheSameFile() {
+        TestFile subDir = tmpDir.file('sub')
+        TestFile childFile = tmpDir.file('sub/a/b.jar')
+
+        factory.addPathVariable('SUB_DIR', subDir)
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+
+        expect:
+
+        // Using files
+        factory.path(childFile) == factory.path(childFile)
+        factory.path(childFile) != factory.path(subDir)
+
+        // Using normalised absolute urls
+        factory.path(childFile) == factory.path("jar://${relpath(childFile)}!/")
+        factory.path("jar://${relpath(childFile)}!/entry") == factory.path("jar://${relpath(childFile)}!/entry")
+        factory.path(childFile) != factory.path("jar://${relpath(childFile)}!/entry")
+
+        // Using replacement variables
+        factory.path(childFile) == factory.path('jar://$SUB_DIR$/a/b.jar!/')
+        factory.path(childFile) == factory.path('jar://$SUB_DIR$/c/../a/b.jar!/')
+
+        factory.path(childFile) != factory.path('jar://$SUB_DIR$/a/b.jar')
+        factory.path(childFile) != factory.path("file://${relpath(childFile)}")
+    }
+
+    private String relpath(File file) {
+        return file.absolutePath.replace(File.separator, '/')
+    }
+
 }
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathTest.groovy
index 44390ef..17596cc 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathTest.groovy
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/PathTest.groovy
@@ -93,9 +93,9 @@ class PathTest extends Specification {
         path.relPath == relpath
     }
 
-    def pathsAreEqualWhenTheyHaveTheSameUrl() {
+    def pathsAreEqualWhenTheyHaveTheSameCanonicalUrl() {
         expect:
-        Matchers.strictlyEquals(new Path(tmpDir.dir, '$ROOT_DIR$', tmpDir.file('file')), new Path('file://$ROOT_DIR$/file'))
+        Matchers.strictlyEquals(new Path('file://$ROOT_DIR$/file'), new Path('file://$ROOT_DIR$/file'))
         new Path('file://$ROOT_DIR$/file') != new Path('file://$ROOT_DIR$/other')
     }
 
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy
index e8e1261..ac6f32a 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy
@@ -15,8 +15,6 @@
  */
 package org.gradle.plugins.idea.model
 
-import org.gradle.api.Action
-import org.gradle.api.artifacts.maven.XmlProvider
 import org.gradle.api.internal.XmlTransformer
 import spock.lang.Specification
 
@@ -24,118 +22,69 @@ import spock.lang.Specification
  * @author Hans Dockter
  */
 class ProjectTest extends Specification {
-    Project project
+    final PathFactory pathFactory = new PathFactory()
+    final customModules = [new ModulePath(path('file://$PROJECT_DIR$/gradle-idea-plugin.iml'), '$PROJECT_DIR$/gradle-idea-plugin.iml')] as Set
+    final customWildcards = ["?*.gradle", "?*.grails"] as Set
+    Project project = new Project(new XmlTransformer(), pathFactory)
 
-    def initWithReaderAndNoJdkAndNoWildcards() {
-        project = createProject(javaVersion: "1.4", reader: customProjectReader)
-
-        expect:
-        project.modulePaths == [new ModulePath('file://$PROJECT_DIR$/gradle-idea-plugin.iml', '$PROJECT_DIR$/gradle-idea-plugin.iml')] as Set
-        project.wildcards == ["?*.gradle", "?*.grails"] as Set
-        project.jdk == new Jdk(true, false, "JDK_1_4", "1.4")
-    }
-
-    def initWithReaderAndJdkAndWildcards_shouldBeMerged() {
-        project = createProject(wildcards: ['?*.groovy'] as Set, reader: customProjectReader)
-
-        expect:
-        project.modulePaths == [new ModulePath('file://$PROJECT_DIR$/gradle-idea-plugin.iml', '$PROJECT_DIR$/gradle-idea-plugin.iml')] as Set
-        project.wildcards == ["?*.gradle", "?*.grails", "?*.groovy"] as Set
-        project.jdk == new Jdk("1.6")
-    }
-
-    def initWithNullReader_shouldUseDefaults() {
-        project = createProject(wildcards: ['!?*.groovy'] as Set)
-
-        expect:
-        project.modulePaths.size() == 0
-        project.wildcards == ["!?*.groovy"] as Set
-        project.jdk == new Jdk("1.6")
-    }
-
-    def toXml_shouldContainCustomValues() {
+    def loadFromReader() {
         when:
-        project = createProject(wildcards: ['?*.groovy'] as Set, reader: customProjectReader)
+        project.load(customProjectReader)
 
         then:
-        project == createProject(wildcards: ['?*.groovy'] as Set, reader: toXmlReader)
+        project.modulePaths == customModules
+        project.wildcards == customWildcards
+        project.jdk == new Jdk(true, false, null, "1.4")
     }
 
-    def toXml_shouldContainSkeleton() {
+    def customJdkAndWildcards_shouldBeMerged() {
+        def modules = [new ModulePath(path('file://$PROJECT_DIR$/other.iml'), '$PROJECT_DIR$/other.iml')] as Set
+
         when:
-        project = createProject(reader: customProjectReader)
+        project.load(customProjectReader)
+        project.configure(modules, '1.6', ['?*.groovy'] as Set)
 
         then:
-        new XmlParser().parse(toXmlReader).toString() == project.xml.toString()
+        project.modulePaths == customModules + modules
+        project.wildcards == customWildcards + ['?*.groovy'] as Set
+        project.jdk == new Jdk("1.6")
     }
 
-    def beforeConfigured() {
-        Action beforeConfiguredActions = { Project ideaProject ->
-            ideaProject.modulePaths.clear()
-        } as Action
-        def modulePaths = [new ModulePath("a", "b")] as Set
-
+    def loadDefaults() {
         when:
-        project = createProject(modulePaths: modulePaths, reader: customProjectReader, beforeConfiguredActions: beforeConfiguredActions)
+        project.loadDefaults()
 
         then:
-        createProject(reader: toXmlReader).modulePaths == modulePaths
+        project.modulePaths.size() == 0
+        project.wildcards == [] as Set
+        project.jdk == new Jdk(true, true, "JDK_1_5", null)
     }
 
-    def whenConfigured() {
-        def moduleFromInitialXml = null
-        def moduleFromProjectConstructor = new ModulePath("a", "b")
-        def moduleAddedInWhenConfiguredAction = new ModulePath("c", "d")
-        Action beforeConfiguredActions = { Project ideaProject ->
-            moduleFromInitialXml = (ideaProject.modulePaths as List)[0]
-        } as Action
-        Action whenConfiguredActions = { Project ideaProject ->
-            assert ideaProject.modulePaths.contains(moduleFromInitialXml)
-            assert ideaProject.modulePaths.contains(moduleFromProjectConstructor)
-            ideaProject.modulePaths.add(moduleAddedInWhenConfiguredAction)
-        } as Action
-
+    def toXml_shouldContainCustomValues() {
         when:
-        project = createProject(modulePaths: [moduleFromProjectConstructor] as Set, reader: customProjectReader,
-                beforeConfiguredActions: beforeConfiguredActions,
-                whenConfiguredActions: whenConfiguredActions)
+        project.loadDefaults()
+        project.configure([] as Set, '1.5', ['?*.groovy'] as Set)
+        def xml = toXmlReader
+        def other = new Project(new XmlTransformer(), pathFactory)
+        other.load(xml)
 
         then:
-        createProject(reader: toXmlReader).modulePaths == [moduleFromInitialXml, moduleFromProjectConstructor, moduleAddedInWhenConfiguredAction] as Set
+        project.wildcards == other.wildcards
+        project.modulePaths == other.modulePaths
+        project.jdk == other.jdk
     }
 
-    private StringReader getToXmlReader() {
-        StringWriter toXmlText = new StringWriter()
-        project.toXml(toXmlText)
-        return new StringReader(toXmlText.toString())
-    }
-
-    def withXml() {
-        XmlTransformer withXmlActions = new XmlTransformer()
-        project = createProject(reader: customProjectReader, withXmlActions: withXmlActions)
-
-        when:
-        def modifiedVersion
-        withXmlActions.addAction { XmlProvider provider ->
-            def xml = provider.asNode()
-            xml. at version += 'x'
-            modifiedVersion = xml. at version
-        }
-
-        then:
-        new XmlParser().parse(toXmlReader). at version == modifiedVersion
+    private InputStream getToXmlReader() {
+        ByteArrayOutputStream toXmlText = new ByteArrayOutputStream()
+        project.store(toXmlText)
+        return new ByteArrayInputStream(toXmlText.toByteArray())
     }
 
-    private InputStreamReader getCustomProjectReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('customProject.xml'))
+    private InputStream getCustomProjectReader() {
+        return getClass().getResourceAsStream('customProject.xml')
     }
 
-    private Project createProject(Map customArgs) {
-        Action dummyBroadcast = Mock()
-        XmlTransformer xmlTransformer = new XmlTransformer()
-        Map args = [modulePaths: [] as Set, javaVersion: "1.6", wildcards: [] as Set, reader: null,
-                beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: xmlTransformer] + customArgs
-        return new Project(args.modulePaths, args.javaVersion, args.wildcards, args.reader,
-                args.beforeConfiguredActions, args.whenConfiguredActions, args.withXmlActions)
+    private Path path(String url) {
+        pathFactory.path(url)
     }
 }
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/WorkspaceTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/WorkspaceTest.groovy
deleted file mode 100644
index 7612965..0000000
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/WorkspaceTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.plugins.idea.model
-
-import org.gradle.api.artifacts.maven.XmlProvider
-import org.gradle.api.internal.XmlTransformer
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter
- */
-
-class WorkspaceTest extends Specification {
-    Workspace workspace
-
-    def toXmlWithCustomReader() {
-        when:
-        workspace = createWorkspace(reader: customWorkspaceReader)
-
-        then:
-        new XmlParser().parse(toXmlReader).toString() == new XmlParser().parse(customWorkspaceReader).toString()
-    }
-
-    def toXmlWithNullReader() {
-        when:
-        workspace = createWorkspace([:])
-
-        then:
-        new XmlParser().parse(toXmlReader).toString() == new XmlParser().parse(defaultWorkspaceReader).toString()
-    }
-    
-    private StringReader getToXmlReader() {
-        StringWriter toXmlText = new StringWriter()
-        workspace.toXml(toXmlText)
-        return new StringReader(toXmlText.toString())
-    }
-
-    def withXml() {
-        XmlTransformer withXmlActions = new XmlTransformer()
-        workspace = createWorkspace(reader: customWorkspaceReader, withXmlActions: withXmlActions)
-
-        when:
-        def modifiedVersion
-        withXmlActions.addAction { XmlProvider provider ->
-            def xml = provider.asNode()
-            xml. at version += 'x'
-            modifiedVersion = xml. at version
-        }
-
-        then:
-        new XmlParser().parse(toXmlReader). at version == modifiedVersion
-    }
-
-    private InputStreamReader getCustomWorkspaceReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('customWorkspace.xml'))
-    }
-
-    private InputStreamReader getDefaultWorkspaceReader() {
-        return new InputStreamReader(getClass().getResourceAsStream('defaultWorkspace.xml'))
-    }
-
-    private Workspace createWorkspace(Map customArgs) {
-        XmlTransformer dummyBroadcast = new XmlTransformer()
-        Map args = [reader: null, withXmlActions: dummyBroadcast] + customArgs
-        return new Workspace(args.reader, args.withXmlActions)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java
index b400da0..d39a155 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java
@@ -15,7 +15,9 @@
  */
 package org.gradle.launcher;
 
-import org.gradle.*;
+import org.gradle.CommandLineArgumentException;
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
 import org.gradle.api.internal.project.ServiceRegistry;
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication;
@@ -23,33 +25,37 @@ import org.gradle.initialization.*;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.util.GradleVersion;
 
+import java.io.File;
 import java.io.PrintStream;
+import java.lang.management.ManagementFactory;
 import java.util.List;
 
 /**
- * Responsible for converting a set of command-line arguments into a {@link Runnable} action.
+ * <p>Responsible for converting a set of command-line arguments into a {@link Runnable} action.</p>
  */
 public class CommandLineActionFactory {
     private static final String HELP = "h";
     private static final String GUI = "gui";
     private static final String VERSION = "v";
-    private final BuildCompleter buildCompleter;
-
-    public CommandLineActionFactory(BuildCompleter buildCompleter) {
-        this.buildCompleter = buildCompleter;
-    }
+    private static final String FOREGROUND = "foreground";
+    private static final String DAEMON = "daemon";
+    private static final String NO_DAEMON = "no-daemon";
+    private static final String STOP = "stop";
 
     /**
-     * Converts the given command-line arguments to a {@code Runnable} action which performs the action requested by the
+     * <p>Converts the given command-line arguments to a {@code Runnable} action which performs the action requested by the
      * command-line args. Does not have any side-effects. Each action will call the supplied {@link
-     * org.gradle.launcher.BuildCompleter} once it has completed.
+     * ExecutionListener} once it has completed.</p>
+     *
+     * <p>Implementation note: attempt to defer as much as possible until action execution time.</p>
      *
      * @param args The command-line arguments.
      * @return The action to execute.
      */
-    Runnable convert(List<String> args) {
+    Action<ExecutionListener> convert(List<String> args) {
         CommandLineParser parser = new CommandLineParser();
 
         CommandLineConverter<StartParameter> startParameterConverter = createStartParameterConverter();
@@ -58,11 +64,15 @@ public class CommandLineActionFactory {
         parser.option(HELP, "?", "help").hasDescription("Shows this help message");
         parser.option(VERSION, "version").hasDescription("Print version info.");
         parser.option(GUI).hasDescription("Launches a GUI application");
+        parser.option(FOREGROUND).hasDescription("Starts the Gradle daemon in the foreground [experimental].");
+        parser.option(DAEMON).hasDescription("Uses the Gradle daemon to run the build. Starts the daemon if not running [experimental].");
+        parser.option(NO_DAEMON).hasDescription("Do not use the Gradle daemon to run the build [experimental].");
+        parser.option(STOP).hasDescription("Stops the Gradle daemon if it is running [experimental].");
 
         LoggingConfiguration loggingConfiguration = new LoggingConfiguration();
         ServiceRegistry loggingServices = createLoggingServices();
 
-        Runnable action;
+        Action<ExecutionListener> action;
         try {
             ParsedCommandLine commandLine = parser.parse(args);
             CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = loggingServices.get(CommandLineConverter.class);
@@ -83,26 +93,41 @@ public class CommandLineActionFactory {
         return new LoggingServiceRegistry();
     }
 
-    GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
-        return new DefaultGradleLauncherFactory(loggingServices);
-    }
-
-    private Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine, CommandLineConverter<StartParameter> startParameterConverter, ServiceRegistry loggingServices) {
+    private Action<ExecutionListener> createAction(CommandLineParser parser, final ParsedCommandLine commandLine, CommandLineConverter<StartParameter> startParameterConverter, final ServiceRegistry loggingServices) {
         if (commandLine.hasOption(HELP)) {
-            return new ShowUsageAction(parser);
+            return new ActionAdapter(new ShowUsageAction(parser));
         }
         if (commandLine.hasOption(VERSION)) {
-            return new ShowVersionAction();
+            return new ActionAdapter(new ShowVersionAction());
         }
         if (commandLine.hasOption(GUI)) {
-            return new ShowGuiAction();
+            return new ActionAdapter(new ShowGuiAction());
+        }
+
+        StartParameter startParameter = new StartParameter();
+        startParameterConverter.convert(commandLine, startParameter);
+        DaemonConnector connector = new DaemonConnector(startParameter.getGradleUserHomeDir());
+        GradleLauncherMetaData clientMetaData = new GradleLauncherMetaData();
+        long startTime = ManagementFactory.getRuntimeMXBean().getStartTime();
+
+        if (commandLine.hasOption(FOREGROUND)) {
+            return new ActionAdapter(new DaemonMain(loggingServices, connector));
+        }
+        if (commandLine.hasOption(STOP)) {
+            return new StopDaemonAction(connector, loggingServices.get(OutputEventListener.class), clientMetaData);
+        }
+
+        boolean useDaemon = System.getProperty("org.gradle.daemon", "false").equals("true");
+        useDaemon = useDaemon || commandLine.hasOption(DAEMON);
+        useDaemon = useDaemon && !commandLine.hasOption(NO_DAEMON);
+        if (useDaemon) {
+            return new DaemonBuildAction(loggingServices.get(OutputEventListener.class), connector, commandLine, new File(System.getProperty("user.dir")), clientMetaData, startTime);
         }
 
-        StartParameter startParameter = startParameterConverter.convert(commandLine);
-        return new RunBuildAction(startParameter, loggingServices);
+        return new RunBuildAction(startParameter, loggingServices, new DefaultBuildRequestMetaData(clientMetaData, startTime));
     }
 
-    private void showUsage(PrintStream out, CommandLineParser parser) {
+    private static void showUsage(PrintStream out, CommandLineParser parser) {
         out.println();
         out.print("USAGE: ");
         new GradleLauncherMetaData().describeCommand(out, "[option...]", "[task...]");
@@ -112,7 +137,7 @@ public class CommandLineActionFactory {
         out.println();
     }
 
-    private class CommandLineParseFailureAction implements Runnable {
+    private static class CommandLineParseFailureAction implements Action<ExecutionListener> {
         private final Exception e;
         private final CommandLineParser parser;
 
@@ -121,15 +146,15 @@ public class CommandLineActionFactory {
             this.e = e;
         }
 
-        public void run() {
+        public void execute(ExecutionListener executionListener) {
             System.err.println();
             System.err.println(e.getMessage());
             showUsage(System.err, parser);
-            buildCompleter.exit(e);
+            executionListener.onFailure(e);
         }
     }
 
-    private class ShowUsageAction implements Runnable {
+    private static class ShowUsageAction implements Runnable {
         private final CommandLineParser parser;
 
         public ShowUsageAction(CommandLineParser parser) {
@@ -138,58 +163,50 @@ public class CommandLineActionFactory {
 
         public void run() {
             showUsage(System.out, parser);
-            buildCompleter.exit(null);
         }
     }
 
-    private class ShowVersionAction implements Runnable {
+    private static class ShowVersionAction implements Runnable {
         public void run() {
             System.out.println(new GradleVersion().prettyPrint());
-            buildCompleter.exit(null);
         }
     }
 
-    class ShowGuiAction implements Runnable {
+    static class ShowGuiAction implements Runnable {
         public void run() {
             BlockingApplication.launchAndBlock();
-            buildCompleter.exit(null);
         }
     }
 
-    private class RunBuildAction implements Runnable {
-        private final StartParameter startParameter;
-        private final ServiceRegistry loggingServices;
+    static class ActionAdapter implements Action<ExecutionListener> {
+        private final Runnable action;
 
-        public RunBuildAction(StartParameter startParameter, ServiceRegistry loggingServices) {
-            this.startParameter = startParameter;
-            this.loggingServices = loggingServices;
+        ActionAdapter(Runnable action) {
+            this.action = action;
         }
 
-        public void run() {
-            GradleLauncherFactory gradleLauncherFactory = createGradleLauncherFactory(loggingServices);
-            GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter);
-            BuildResult buildResult = gradleLauncher.run();
-            buildCompleter.exit(buildResult.getFailure());
+        public void execute(ExecutionListener executionListener) {
+            action.run();
         }
     }
 
-    class WithLoggingAction implements Runnable {
+    static class WithLoggingAction implements Action<ExecutionListener> {
         private final LoggingConfiguration loggingConfiguration;
         private final ServiceRegistry loggingServices;
-        private final Runnable action;
+        private final Action<ExecutionListener> action;
 
-        public WithLoggingAction(LoggingConfiguration loggingConfiguration, ServiceRegistry loggingServices, Runnable action) {
+        public WithLoggingAction(LoggingConfiguration loggingConfiguration, ServiceRegistry loggingServices, Action<ExecutionListener> action) {
             this.loggingConfiguration = loggingConfiguration;
             this.loggingServices = loggingServices;
             this.action = action;
         }
 
-        public void run() {
+        public void execute(ExecutionListener executionListener) {
             LoggingManagerInternal loggingManager = loggingServices.getFactory(LoggingManagerInternal.class).create();
             loggingManager.setLevel(loggingConfiguration.getLogLevel());
             loggingManager.colorStdOutAndStdErr(loggingConfiguration.isColorOutput());
             loggingManager.start();
-            action.run();
+            action.execute(executionListener);
         }
     }
 }
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonBuildAction.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonBuildAction.java
new file mode 100644
index 0000000..1933522
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonBuildAction.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.ParsedCommandLine;
+import org.gradle.launcher.protocol.Build;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.remote.internal.Connection;
+
+import java.io.File;
+
+public class DaemonBuildAction extends DaemonClientAction implements Action<ExecutionListener> {
+    private static final Logger LOGGER = Logging.getLogger(DaemonBuildAction.class);
+    private final DaemonConnector connector;
+    private final ParsedCommandLine args;
+    private final File currentDir;
+    private final BuildClientMetaData clientMetaData;
+    private final long startTime;
+
+    public DaemonBuildAction(OutputEventListener outputEventListener, DaemonConnector connector, ParsedCommandLine args, File currentDir, BuildClientMetaData clientMetaData, long startTime) {
+        super(outputEventListener);
+        this.connector = connector;
+        this.args = args;
+        this.currentDir = currentDir;
+        this.clientMetaData = clientMetaData;
+        this.startTime = startTime;
+    }
+
+    public void execute(ExecutionListener executionListener) {
+        LOGGER.warn("Note: the Gradle build daemon is an experimental feature.");
+        LOGGER.warn("As such, you may experience unexpected build failures. You may need to occasionally stop the daemon.");
+        Connection<Object> connection = connector.connect();
+        run(new Build(currentDir, args, startTime, clientMetaData), connection, executionListener);
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonClientAction.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonClientAction.java
new file mode 100644
index 0000000..219f574
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonClientAction.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.api.Action;
+import org.gradle.launcher.protocol.Command;
+import org.gradle.launcher.protocol.CommandComplete;
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.remote.internal.Connection;
+
+/**
+ * The client piece of the build daemon.
+ *
+ * <p>Protocol is this:</p>
+ *
+ * <ol> <li>Client connects to the server.</li>
+ *
+ * <li>Client sends a {@link org.gradle.launcher.protocol.Command} message.</li>
+ *
+ * <li>Server sends zero or more {@link org.gradle.logging.internal.OutputEvent} messages. Note that the server may
+ * send output messages before it receives the command message.
+ * </li>
+ *
+ * <li>Server sends a {@link org.gradle.launcher.protocol.CommandComplete} message.</li>
+ *
+ * <li>Connection is closed.</li>
+ *
+ * </ol>
+ */
+public abstract class DaemonClientAction implements Action<ExecutionListener> {
+    protected final OutputEventListener outputEventListener;
+
+    public DaemonClientAction(OutputEventListener outputEventListener) {
+        this.outputEventListener = outputEventListener;
+    }
+
+    protected void run(Command command, Connection<Object> connection, ExecutionListener executionListener) {
+        try {
+            connection.dispatch(command);
+
+            while (true) {
+                Object object = connection.receive();
+                if (object instanceof CommandComplete) {
+                    CommandComplete commandComplete = (CommandComplete) object;
+                    if (commandComplete.getFailure() != null) {
+                        executionListener.onFailure(commandComplete.getFailure());
+                    }
+                    break;
+                }
+                OutputEvent outputEvent = (OutputEvent) object;
+                outputEventListener.onOutput(outputEvent);
+            }
+        } finally {
+            connection.stop();
+        }
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonConnector.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonConnector.java
new file mode 100644
index 0000000..d962a9d
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonConnector.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.internal.ConnectException;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.TcpIncomingConnector;
+import org.gradle.messaging.remote.internal.TcpOutgoingConnector;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.Jvm;
+import org.gradle.util.UncheckedException;
+
+import java.io.*;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DaemonConnector {
+    private static final Logger LOGGER = Logging.getLogger(DaemonConnector.class);
+    private final File userHomeDir;
+
+    public DaemonConnector(File userHomeDir) {
+        this.userHomeDir = userHomeDir;
+    }
+
+    /**
+     * Attempts to connect to the daemon, if it is running.
+     *
+     * @return The connection, or null if not running.
+     */
+    Connection<Object> maybeConnect() {
+        try {
+            URI uri;
+            try {
+                FileInputStream inputStream = new FileInputStream(getRegistryFile());
+                try {
+                    // Acquire shared lock on file while reading it
+                    inputStream.getChannel().lock(0, Long.MAX_VALUE, true);
+                    DataInputStream dataInputStream = new DataInputStream(inputStream);
+                    uri = new URI(dataInputStream.readUTF());
+                } finally {
+                    // Also releases the lock
+                    inputStream.close();
+                }
+            } catch (FileNotFoundException e) {
+                // Ignore
+                return null;
+            }
+            try {
+                return new TcpOutgoingConnector(getClass().getClassLoader()).connect(uri);
+            } catch (ConnectException e) {
+                // Ignore
+                return null;
+            }
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+    }
+
+    /**
+     * Connects to the daemon, starting it if required.
+     *
+     * @return The connection. Never returns null.
+     */
+    Connection<Object> connect() {
+        Connection<Object> connection = maybeConnect();
+        if (connection != null) {
+            return connection;
+        }
+
+        LOGGER.info("Starting Gradle daemon");
+        try {
+            startDaemon();
+            Date expiry = new Date(System.currentTimeMillis() + 30000L);
+            do {
+                connection = maybeConnect();
+                if (connection != null) {
+                    return connection;
+                }
+                Thread.sleep(200L);
+            } while (System.currentTimeMillis() < expiry.getTime());
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+
+        throw new GradleException("Timeout waiting to connect to Gradle daemon.");
+    }
+
+    private void startDaemon() throws IOException {
+        List<String> daemonArgs = new ArrayList<String>();
+        daemonArgs.add(Jvm.current().getJavaExecutable().getAbsolutePath());
+        daemonArgs.add("-Xmx1024m");
+        daemonArgs.add("-XX:MaxPermSize=256m");
+        daemonArgs.add("-cp");
+        daemonArgs.add(GUtil.join(new DefaultClassPathRegistry().getClassPathFiles("GRADLE_RUNTIME"),
+                File.pathSeparator));
+        daemonArgs.add(GradleDaemon.class.getName());
+        daemonArgs.add(String.format("-%s", DefaultCommandLineConverter.GRADLE_USER_HOME));
+        daemonArgs.add(userHomeDir.getAbsolutePath());
+        ProcessBuilder builder = new ProcessBuilder(daemonArgs);
+        builder.directory(userHomeDir);
+        userHomeDir.mkdirs();
+        Process process = builder.start();
+        process.getOutputStream().close();
+        process.getErrorStream().close();
+        process.getInputStream().close();
+    }
+
+    /**
+     * Starts accepting connections.
+     *
+     * @param handler The handler for connections.
+     */
+    void accept(final IncomingConnectionHandler handler) {
+        DefaultExecutorFactory executorFactory = new DefaultExecutorFactory();
+        TcpIncomingConnector incomingConnector = new TcpIncomingConnector(executorFactory, getClass().getClassLoader());
+        final CompletionHandler finished = new CompletionHandler();
+
+        LOGGER.lifecycle("Awaiting requests.");
+        
+        URI uri = incomingConnector.accept(new Action<ConnectEvent<Connection<Object>>>() {
+            public void execute(ConnectEvent<Connection<Object>> connectionConnectEvent) {
+                try {
+                    finished.onStartActivity();
+                    handler.handle(connectionConnectEvent.getConnection(), finished);
+                } finally {
+                    finished.onActivityComplete();
+                    connectionConnectEvent.getConnection().stop();
+                }
+            }
+        });
+
+        try {
+            File registryFile = getRegistryFile();
+            registryFile.getParentFile().mkdirs();
+//            registryFile.createNewFile();
+            FileOutputStream outputStream = new FileOutputStream(registryFile);
+            try {
+                // Lock file while writing to it
+                outputStream.getChannel().lock();
+                DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
+                dataOutputStream.writeUTF(uri.toString());
+                dataOutputStream.flush();
+            } finally {
+                // Also releases the lock
+                outputStream.close();
+            }
+        } catch (IOException e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+
+        boolean stopped = finished.awaitStop();
+        if (!stopped) {
+            LOGGER.lifecycle("Time-out waiting for requests. Stopping.");
+        }
+        new CompositeStoppable(incomingConnector, executorFactory).stop();
+
+        getRegistryFile().delete();
+    }
+
+    private File getRegistryFile() {
+        return new File(userHomeDir, String.format("daemon/%s/registry.bin", new GradleVersion().getVersion()));
+    }
+
+    private static class CompletionHandler implements Stoppable {
+        private static final int THREE_HOURS = 3 * 60 * 60 * 1000;
+        private final Lock lock = new ReentrantLock();
+        private final Condition condition = lock.newCondition();
+        private boolean running;
+        private boolean stopped;
+        private long expiry;
+
+        CompletionHandler() {
+            onActivityComplete();
+        }
+
+        /**
+         * Waits until stopped, or timeout.
+         * @return true if stopped, false if timeout
+         */
+        public boolean awaitStop() {
+            lock.lock();
+            try {
+                while (running || (!stopped && System.currentTimeMillis() < expiry)) {
+                    try {
+                        condition.awaitUntil(new Date(expiry));
+                    } catch (InterruptedException e) {
+                        throw UncheckedException.asUncheckedException(e);
+                    }
+                }
+                assert !running;
+                return stopped;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public void stop() {
+            lock.lock();
+            try {
+                stopped = true;
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public void onStartActivity() {
+            lock.lock();
+            try {
+                assert !running;
+                running = true;
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public void onActivityComplete() {
+            lock.lock();
+            try {
+                running = false;
+                expiry = System.currentTimeMillis() + THREE_HOURS;
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonMain.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonMain.java
new file mode 100644
index 0000000..1d62ff0
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/DaemonMain.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.BuildExceptionReporter;
+import org.gradle.initialization.DefaultBuildRequestMetaData;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.StartParameter;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.launcher.protocol.Build;
+import org.gradle.launcher.protocol.Command;
+import org.gradle.launcher.protocol.CommandComplete;
+import org.gradle.launcher.protocol.Stop;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.logging.internal.LoggingOutputInternal;
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.messaging.remote.internal.Connection;
+
+import java.util.Arrays;
+import java.util.Properties;
+
+/**
+ * The server portion of the build daemon. See {@link DaemonClientAction} for a description of the
+ * protocol.
+ */
+public class DaemonMain implements Runnable {
+    private static final Logger LOGGER = Logging.getLogger(Main.class);
+    private final ServiceRegistry loggingServices;
+    private final DaemonConnector connector;
+    private final GradleLauncherFactory launcherFactory;
+
+    public DaemonMain(ServiceRegistry loggingServices, DaemonConnector connector) {
+        this.loggingServices = loggingServices;
+        this.connector = connector;
+        launcherFactory = new DefaultGradleLauncherFactory(loggingServices);
+    }
+
+    public static void main(String[] args) {
+        StartParameter startParameter = new DefaultCommandLineConverter().convert(Arrays.asList(args));
+        DaemonConnector connector = new DaemonConnector(startParameter.getGradleUserHomeDir());
+        new DaemonMain(new LoggingServiceRegistry(), connector).run();
+    }
+
+    public void run() {
+        connector.accept(new IncomingConnectionHandler() {
+            public void handle(Connection<Object> connection, Stoppable serverControl) {
+                doRun(connection, serverControl);
+            }
+        });
+    }
+
+    private void doRun(final Connection<Object> connection, Stoppable serverControl) {
+        ExecutionListenerImpl executionListener = new ExecutionListenerImpl();
+        try {
+            LoggingOutputInternal loggingOutput = loggingServices.get(LoggingOutputInternal.class);
+            OutputEventListener listener = new OutputEventListener() {
+                public void onOutput(OutputEvent event) {
+                    connection.dispatch(event);
+                }
+            };
+
+            // Perform as much as possible of the interaction while the logging is routed to the client
+            loggingOutput.addOutputEventListener(listener);
+            try {
+                doRunWithLogging(connection, serverControl, executionListener);
+            } finally {
+                loggingOutput.removeOutputEventListener(listener);
+            }
+        } catch (Throwable throwable) {
+            LOGGER.error("Could not execute build.", throwable);
+            executionListener.onFailure(throwable);
+        }
+        connection.dispatch(new CommandComplete(executionListener.failure));
+    }
+
+    private void doRunWithLogging(Connection<Object> connection, Stoppable serverControl, ExecutionListener executionListener) {
+        Command command = (Command) connection.receive();
+        try {
+            doRunWithExceptionHandling(command, serverControl, executionListener);
+        } catch (Throwable throwable) {
+            BuildExceptionReporter exceptionReporter = new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), new StartParameter(), command.getClientMetaData());
+            exceptionReporter.reportException(throwable);
+            executionListener.onFailure(throwable);
+        }
+    }
+
+    private void doRunWithExceptionHandling(Command command, Stoppable serverControl, ExecutionListener executionListener) {
+        LOGGER.info("Executing {}", command);
+        if (command instanceof Stop) {
+            LOGGER.lifecycle("Stopping");
+            serverControl.stop();
+            return;
+        }
+
+        assert command instanceof Build;
+        build((Build) command, executionListener);
+    }
+
+    private void build(Build build, ExecutionListener executionListener) {
+        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
+        StartParameter startParameter = new StartParameter();
+        startParameter.setCurrentDir(build.getCurrentDir());
+        converter.convert(build.getArgs(), startParameter);
+        LoggingManagerInternal loggingManager = loggingServices.getFactory(LoggingManagerInternal.class).create();
+        loggingManager.setLevel(startParameter.getLogLevel());
+        loggingManager.start();
+
+        Properties sysProperties = new Properties();
+        sysProperties.putAll(System.getProperties());
+
+        try {
+            RunBuildAction action = new RunBuildAction(startParameter, loggingServices, new DefaultBuildRequestMetaData(build.getClientMetaData(), build.getStartTime())) {
+                @Override
+                GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
+                    return launcherFactory;
+                }
+            };
+            action.execute(executionListener);
+        } catch (Throwable throwable) {
+            BuildExceptionReporter exceptionReporter = new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), new StartParameter(), build.getClientMetaData());
+            exceptionReporter.reportException(throwable);
+            executionListener.onFailure(throwable);
+        }
+
+        loggingManager.stop();
+
+        System.setProperties(sysProperties);
+    }
+
+    private static class ExecutionListenerImpl implements ExecutionListener {
+        public Throwable failure;
+
+        public void onFailure(Throwable failure) {
+            this.failure = failure;
+        }
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/ExecutionListener.java
similarity index 54%
copy from subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
copy to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/ExecutionListener.java
index d8bb8b6..e2e8ab1 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/ExecutionListener.java
@@ -15,6 +15,18 @@
  */
 package org.gradle.launcher;
 
-public interface BuildCompleter {
-    void exit(Throwable failure);
+/**
+ * Allows an execution action to provide status information to the execution context.
+ *
+ * <p>Note: if the action does not call {@link #onFailure(Throwable)}, then the execution is assumed to have
+ * succeeded.</p>
+ */
+public interface ExecutionListener {
+    /**
+     * Reports a failure of the execution. Note that it is the caller's responsibility to perform any logging of the
+     * failure.
+     *
+     * @param failure The execution failure. This exception has already been logged.
+     */
+    void onFailure(Throwable failure);
 }
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleDaemon.java
similarity index 80%
copy from subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
copy to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleDaemon.java
index d8bb8b6..840bbf4 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleDaemon.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.launcher;
 
-public interface BuildCompleter {
-    void exit(Throwable failure);
+public class GradleDaemon {
+    public static void main(String[] args) {
+        new ProcessBootstrap().run("org.gradle.launcher.DaemonMain", args);
+    }
 }
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
index e30b620..5ed8c10 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
@@ -16,52 +16,11 @@
 
 package org.gradle.launcher;
 
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.DefaultClassPathRegistry;
-
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Arrays;
-
 /**
  * @author Steven Devijver, Hans Dockter
  */
 public class GradleMain {
-
     public static void main(String[] args) throws Exception {
-        String bootStrapDebugValue = System.getProperty("gradle.bootstrap.debug");
-        boolean bootStrapDebug = bootStrapDebugValue != null && !bootStrapDebugValue.toUpperCase().equals("FALSE");
-
-        processGradleUserHome(bootStrapDebug);
-
-        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry();
-        URL[] antClasspath = classPathRegistry.getClassPathUrls("ANT");
-        URL[] runtimeClasspath = classPathRegistry.getClassPathUrls("GRADLE_RUNTIME");
-        ClassLoader rootClassLoader = ClassLoader.getSystemClassLoader().getParent();
-        if (bootStrapDebug) {
-            System.out.println("Parent Classloader of new context classloader is: " + rootClassLoader);
-            System.out.println("Using Ant classpath: " + Arrays.toString(antClasspath));
-            System.out.println("Using runtime classpath: " + Arrays.toString(runtimeClasspath));
-        }
-        URLClassLoader antClassLoader = new URLClassLoader(antClasspath, rootClassLoader);
-        URLClassLoader runtimeClassLoader = new URLClassLoader(runtimeClasspath, antClassLoader);
-        Thread.currentThread().setContextClassLoader(runtimeClassLoader);
-        Class mainClass = runtimeClassLoader.loadClass("org.gradle.launcher.Main");
-        Method mainMethod = mainClass.getMethod("main", String[].class);
-        mainMethod.invoke(null, new Object[]{args});
-    }
-
-    private static void processGradleUserHome(boolean bootStrapDebug) {
-        String gradleUserHome = System.getProperty("gradle.user.home");
-        if (gradleUserHome == null) {
-            gradleUserHome = System.getenv("GRADLE_USER_HOME");
-            if (gradleUserHome != null) {
-                System.setProperty("gradle.user.home", gradleUserHome);
-                if (bootStrapDebug) {
-                    System.out.println("Gradle User Home is declared by environment variable GRADLE_USER_HOME to: " + gradleUserHome);
-                }
-            }
-        }
+        new ProcessBootstrap().run("org.gradle.launcher.Main", args);
     }
 }
\ No newline at end of file
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/IncomingConnectionHandler.java
similarity index 74%
copy from subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
copy to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/IncomingConnectionHandler.java
index d8bb8b6..a030c73 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/IncomingConnectionHandler.java
@@ -15,6 +15,9 @@
  */
 package org.gradle.launcher;
 
-public interface BuildCompleter {
-    void exit(Throwable failure);
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.messaging.remote.internal.Connection;
+
+public interface IncomingConnectionHandler {
+    void handle(Connection<Object> connection, Stoppable serverControl);
 }
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java
index f66e164..f381091 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java
@@ -17,6 +17,8 @@ package org.gradle.launcher;
 
 import org.gradle.BuildExceptionReporter;
 import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.logging.internal.StreamingStyledTextOutputFactory;
 
 import java.util.Arrays;
@@ -46,27 +48,40 @@ public class Main {
     public void execute() {
         BuildCompleter buildCompleter = createBuildCompleter();
         try {
-            CommandLineActionFactory actionFactory = createActionFactory(buildCompleter);
-            Runnable action = actionFactory.convert(Arrays.asList(args));
-            action.run();
-            buildCompleter.exit(null);
+            // We execute as much as possible inside this try block (including construction of dependencies), so that
+            // the error reporting below is applied to as much code as possible
+            CommandLineActionFactory actionFactory = createActionFactory();
+            Action<ExecutionListener> action = actionFactory.convert(Arrays.asList(args));
+            action.execute(buildCompleter);
         } catch (Throwable e) {
-            new BuildExceptionReporter(new StreamingStyledTextOutputFactory(System.err), new StartParameter()).reportException(e);
-            buildCompleter.exit(e);
+            BuildExceptionReporter exceptionReporter = new BuildExceptionReporter(new StreamingStyledTextOutputFactory(System.err), new StartParameter(), new GradleLauncherMetaData());
+            exceptionReporter.reportException(e);
+            buildCompleter.onFailure(e);
         }
+        buildCompleter.exit();
     }
 
-    CommandLineActionFactory createActionFactory(BuildCompleter buildCompleter) {
-        return new CommandLineActionFactory(buildCompleter);
+    CommandLineActionFactory createActionFactory() {
+        return new CommandLineActionFactory();
     }
 
     BuildCompleter createBuildCompleter() {
-        return new ProcessExitBuildCompleter();
+        return new ProcessExitExecutionListener();
     }
 
-    private static class ProcessExitBuildCompleter implements BuildCompleter {
-        public void exit(Throwable failure) {
-            System.exit(failure == null ? 0 : 1);
+    interface BuildCompleter extends ExecutionListener {
+        void exit();
+    }
+
+    static class ProcessExitExecutionListener implements BuildCompleter {
+        private boolean failure;
+
+        public void onFailure(Throwable failure) {
+            this.failure = true;
+        }
+
+        public void exit() {
+            System.exit(failure ? 1 : 0);
         }
     }
 }
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
similarity index 50%
copy from subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
copy to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
index e30b620..ab25e42 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
@@ -1,67 +1,48 @@
-/*
- * Copyright 2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.launcher;
-
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.DefaultClassPathRegistry;
-
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Arrays;
-
-/**
- * @author Steven Devijver, Hans Dockter
- */
-public class GradleMain {
-
-    public static void main(String[] args) throws Exception {
-        String bootStrapDebugValue = System.getProperty("gradle.bootstrap.debug");
-        boolean bootStrapDebug = bootStrapDebugValue != null && !bootStrapDebugValue.toUpperCase().equals("FALSE");
-
-        processGradleUserHome(bootStrapDebug);
-
-        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry();
-        URL[] antClasspath = classPathRegistry.getClassPathUrls("ANT");
-        URL[] runtimeClasspath = classPathRegistry.getClassPathUrls("GRADLE_RUNTIME");
-        ClassLoader rootClassLoader = ClassLoader.getSystemClassLoader().getParent();
-        if (bootStrapDebug) {
-            System.out.println("Parent Classloader of new context classloader is: " + rootClassLoader);
-            System.out.println("Using Ant classpath: " + Arrays.toString(antClasspath));
-            System.out.println("Using runtime classpath: " + Arrays.toString(runtimeClasspath));
-        }
-        URLClassLoader antClassLoader = new URLClassLoader(antClasspath, rootClassLoader);
-        URLClassLoader runtimeClassLoader = new URLClassLoader(runtimeClasspath, antClassLoader);
-        Thread.currentThread().setContextClassLoader(runtimeClassLoader);
-        Class mainClass = runtimeClassLoader.loadClass("org.gradle.launcher.Main");
-        Method mainMethod = mainClass.getMethod("main", String[].class);
-        mainMethod.invoke(null, new Object[]{args});
-    }
-
-    private static void processGradleUserHome(boolean bootStrapDebug) {
-        String gradleUserHome = System.getProperty("gradle.user.home");
-        if (gradleUserHome == null) {
-            gradleUserHome = System.getenv("GRADLE_USER_HOME");
-            if (gradleUserHome != null) {
-                System.setProperty("gradle.user.home", gradleUserHome);
-                if (bootStrapDebug) {
-                    System.out.println("Gradle User Home is declared by environment variable GRADLE_USER_HOME to: " + gradleUserHome);
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class ProcessBootstrap {
+    void run(String mainClassName, String[] args) {
+        try {
+            runNoExit(mainClassName, args);
+            System.exit(0);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private void runNoExit(String mainClassName, String[] args) throws Exception {
+        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry();
+        URL[] antClasspath = classPathRegistry.getClassPathUrls("ANT");
+        URL[] runtimeClasspath = classPathRegistry.getClassPathUrls("GRADLE_RUNTIME");
+        ClassLoader rootClassLoader = ClassLoader.getSystemClassLoader().getParent();
+        URLClassLoader antClassLoader = new URLClassLoader(antClasspath, rootClassLoader);
+        URLClassLoader runtimeClassLoader = new URLClassLoader(runtimeClasspath, antClassLoader);
+        Thread.currentThread().setContextClassLoader(runtimeClassLoader);
+        Class mainClass = runtimeClassLoader.loadClass(mainClassName);
+        Method mainMethod = mainClass.getMethod("main", String[].class);
+        mainMethod.invoke(null, new Object[]{args});
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/RunBuildAction.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/RunBuildAction.java
new file mode 100644
index 0000000..5fee3a7
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/RunBuildAction.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.initialization.BuildRequestMetaData;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+
+public class RunBuildAction implements Action<ExecutionListener> {
+    private final StartParameter startParameter;
+    private final ServiceRegistry loggingServices;
+    private final BuildRequestMetaData requestMetaData;
+
+    public RunBuildAction(StartParameter startParameter, ServiceRegistry loggingServices, BuildRequestMetaData requestMetaData) {
+        this.startParameter = startParameter;
+        this.loggingServices = loggingServices;
+        this.requestMetaData = requestMetaData;
+    }
+
+    public void execute(ExecutionListener executionListener) {
+        GradleLauncherFactory gradleLauncherFactory = createGradleLauncherFactory(loggingServices);
+        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter, requestMetaData);
+        BuildResult buildResult = gradleLauncher.run();
+        Throwable failure = buildResult.getFailure();
+        if (failure != null) {
+            executionListener.onFailure(failure);
+        }
+    }
+
+    GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
+        return new DefaultGradleLauncherFactory(loggingServices);
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/StopDaemonAction.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/StopDaemonAction.java
new file mode 100644
index 0000000..f2518d3
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/StopDaemonAction.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.launcher.protocol.Stop;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.remote.internal.Connection;
+
+public class StopDaemonAction extends DaemonClientAction implements Action<ExecutionListener> {
+    private static final Logger LOGGER = Logging.getLogger(StopDaemonAction.class);
+    private final DaemonConnector connector;
+    private final BuildClientMetaData clientMetaData;
+
+    public StopDaemonAction(DaemonConnector connector, OutputEventListener outputEventListener, BuildClientMetaData clientMetaData) {
+        super(outputEventListener);
+        this.connector = connector;
+        this.clientMetaData = clientMetaData;
+    }
+
+    public void execute(ExecutionListener executionListener) {
+        Connection<Object> connection = connector.maybeConnect();
+        if (connection == null) {
+            LOGGER.lifecycle("Gradle daemon is not running.");
+            return;
+        }
+        run(new Stop(clientMetaData), connection, executionListener);
+        LOGGER.lifecycle("Gradle daemon stopped.");
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Build.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Build.java
new file mode 100644
index 0000000..b79e457
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Build.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher.protocol;
+
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.ParsedCommandLine;
+
+import java.io.File;
+
+public class Build extends Command {
+    private final ParsedCommandLine args;
+    private final File currentDir;
+    private final long startTime;
+
+    public Build(File currentDir, ParsedCommandLine args, long startTime, BuildClientMetaData clientMetaData) {
+        super(clientMetaData);
+        this.currentDir = currentDir;
+        this.args = args;
+        this.startTime = startTime;
+    }
+
+    public ParsedCommandLine getArgs() {
+        return args;
+    }
+
+    public File getCurrentDir() {
+        return currentDir;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+}
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Command.java
similarity index 54%
copy from subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy
copy to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Command.java
index e4b2169..44e702e 100644
--- a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModulePathTest.groovy
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Command.java
@@ -13,16 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.plugins.idea.model
+package org.gradle.launcher.protocol;
 
-import spock.lang.Specification
-import org.gradle.util.Matchers
+import org.gradle.initialization.BuildClientMetaData;
 
-class ModulePathTest extends Specification {
-    def pathsAreEqualWhenTheirUrlAndPathAreEqual() {
-        expect:
-        Matchers.strictlyEquals(new ModulePath('url', 'path'), new ModulePath('url', 'path'))
-        new ModulePath('url', 'path') != new ModulePath('url', 'other')
-        new ModulePath('url', 'path') != new ModulePath('other', 'path')
+import java.io.Serializable;
+
+public class Command implements Serializable {
+    private final BuildClientMetaData clientMetaData;
+
+    public Command(BuildClientMetaData clientMetaData) {
+        this.clientMetaData = clientMetaData;
+    }
+
+    public BuildClientMetaData getClientMetaData() {
+        return clientMetaData;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
     }
 }
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/CommandComplete.java
similarity index 66%
copy from subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
copy to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/CommandComplete.java
index 0647de2..96698a6 100644
--- a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/CommandComplete.java
@@ -1,25 +1,30 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.messaging.remote.internal;
-
-import java.net.URI;
-
-public interface OutgoingConnector {
-    /**
-     * Creates a connection to the given address.
-     */
-    Connection<Message> connect(URI destinationUri);
-}
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher.protocol;
+
+import java.io.Serializable;
+
+public class CommandComplete implements Serializable {
+    private final Throwable failure;
+
+    public CommandComplete(Throwable failure) {
+        this.failure = failure;
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Stop.java
similarity index 73%
rename from subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
rename to subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Stop.java
index d8bb8b6..b0879c2 100644
--- a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/BuildCompleter.java
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/protocol/Stop.java
@@ -13,8 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.launcher;
+package org.gradle.launcher.protocol;
 
-public interface BuildCompleter {
-    void exit(Throwable failure);
+import org.gradle.initialization.BuildClientMetaData;
+
+public class Stop extends Command {
+    public Stop(BuildClientMetaData clientMetaData) {
+        super(clientMetaData);
+    }
 }
diff --git a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy
index 5cadf85..342d4a7 100644
--- a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy
+++ b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy
@@ -15,24 +15,28 @@
  */
 package org.gradle.launcher
 
+import org.gradle.api.internal.Factory
+import org.gradle.api.internal.project.ServiceRegistry
 import org.gradle.initialization.CommandLineConverter
+import org.gradle.launcher.CommandLineActionFactory.ActionAdapter
+import org.gradle.launcher.CommandLineActionFactory.ShowGuiAction
+import org.gradle.launcher.CommandLineActionFactory.WithLoggingAction
+import org.gradle.logging.LoggingConfiguration
+import org.gradle.logging.LoggingManagerInternal
 import org.gradle.util.GradleVersion
 import org.gradle.util.RedirectStdOutAndErr
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Specification
 import org.gradle.*
-import org.gradle.api.internal.project.ServiceRegistry
-import org.gradle.logging.LoggingConfiguration
-import org.gradle.logging.LoggingManagerInternal
-import org.gradle.api.internal.Factory
+import org.gradle.initialization.GradleLauncherFactory
 
 class CommandLineActionFactoryTest extends Specification {
     @Rule
     public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr();
     @Rule
     public final SetSystemProperties sysProperties = new SetSystemProperties();
-    final BuildCompleter buildCompleter = Mock()
+    final ExecutionListener buildCompleter = Mock()
     final CommandLineConverter<StartParameter> startParameterConverter = Mock()
     final GradleLauncherFactory gradleLauncherFactory = Mock()
     final GradleLauncher gradleLauncher = Mock()
@@ -40,7 +44,7 @@ class CommandLineActionFactoryTest extends Specification {
     final ServiceRegistry loggingServices = Mock()
     final CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = Mock()
     final LoggingManagerInternal loggingManager = Mock()
-    final CommandLineActionFactory factory = new CommandLineActionFactory(buildCompleter) {
+    final CommandLineActionFactory factory = new CommandLineActionFactory() {
         @Override
         ServiceRegistry createLoggingServices() {
             return loggingServices
@@ -50,11 +54,6 @@ class CommandLineActionFactoryTest extends Specification {
         CommandLineConverter<StartParameter> createStartParameterConverter() {
             return startParameterConverter
         }
-
-        @Override
-        GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
-            return gradleLauncherFactory
-        }
     }
 
     def setup() {
@@ -73,10 +72,10 @@ class CommandLineActionFactoryTest extends Specification {
 
         then:
         1 * startParameterConverter.configure(!null) >> { args -> args[0].option('some-build-option') }
-        1 * startParameterConverter.convert(!null) >> { throw failure }
+        1 * startParameterConverter.convert(!null, !null) >> { throw failure }
 
         when:
-        action.run()
+        action.execute(buildCompleter)
 
         then:
         1 * loggingManager.start()
@@ -84,13 +83,13 @@ class CommandLineActionFactoryTest extends Specification {
         outputs.stdErr.contains('USAGE: gradle [option...] [task...]')
         outputs.stdErr.contains('--help')
         outputs.stdErr.contains('--some-build-option')
-        1 * buildCompleter.exit(failure)
+        1 * buildCompleter.onFailure(failure)
     }
 
     def displaysUsageMessage() {
         when:
         def action = factory.convert([option])
-        action.run()
+        action.execute(buildCompleter)
 
         then:
         _ * startParameterConverter.configure(!null) >> { args -> args[0].option('some-build-option') }
@@ -98,7 +97,6 @@ class CommandLineActionFactoryTest extends Specification {
         outputs.stdOut.contains('USAGE: gradle [option...] [task...]')
         outputs.stdOut.contains('--help')
         outputs.stdOut.contains('--some-build-option')
-        1 * buildCompleter.exit(null)
 
         where:
         option << ['-h', '-?', '--help']
@@ -109,22 +107,20 @@ class CommandLineActionFactoryTest extends Specification {
 
         when:
         def action = factory.convert(['-?'])
-        action.run()
+        action.execute(buildCompleter)
 
         then:
         outputs.stdOut.contains('USAGE: gradle-app [option...] [task...]')
-        1 * buildCompleter.exit(null)
     }
 
     def displaysVersionMessage() {
         when:
         def action = factory.convert([option])
-        action.run()
+        action.execute(buildCompleter)
 
         then:
         1 * loggingManager.start()
         outputs.stdOut.contains(new GradleVersion().prettyPrint())
-        1 * buildCompleter.exit(null)
 
         where:
         option << ['-v', '--version']
@@ -135,48 +131,90 @@ class CommandLineActionFactoryTest extends Specification {
         def action = factory.convert(['--gui'])
 
         then:
-        action instanceof CommandLineActionFactory.WithLoggingAction
-        action.action instanceof CommandLineActionFactory.ShowGuiAction
+        action instanceof WithLoggingAction
+        action.action instanceof ActionAdapter
+        action.action.action instanceof ShowGuiAction
     }
 
     def executesBuild() {
-        def startParameter = new StartParameter();
+        when:
+        def action = factory.convert(['args'])
 
+        then:
+        action instanceof WithLoggingAction
+        action.action instanceof RunBuildAction
+    }
+
+    def executesBuildUsingDaemon() {
         when:
+        def action = factory.convert(['--daemon', 'args'])
+
+        then:
+        action instanceof WithLoggingAction
+        action.action instanceof DaemonBuildAction
+    }
+
+    def executesBuildUsingDaemonWhenSystemPropertyIsSetToTrue() {
+        when:
+        System.properties['org.gradle.daemon'] = 'false'
         def action = factory.convert(['args'])
 
         then:
-        1 * startParameterConverter.convert(!null) >> startParameter
+        action instanceof WithLoggingAction
+        action.action instanceof RunBuildAction
 
         when:
-        action.run()
+        System.properties['org.gradle.daemon'] = 'true'
+        action = factory.convert(['args'])
 
         then:
-        1 * loggingManager.start()
-        1 * gradleLauncherFactory.newInstance(startParameter) >> gradleLauncher
-        1 * gradleLauncher.run() >> buildResult
-        1 * buildResult.failure >> null
-        1 * buildCompleter.exit(null)
+        action instanceof WithLoggingAction
+        action.action instanceof DaemonBuildAction
     }
 
-    def executesFailedBuild() {
-        def RuntimeException failure = new RuntimeException()
-        def startParameter = new StartParameter();
+    def doesNotUseDaemonWhenNoDaemonOptionPresent() {
+        when:
+        def action = factory.convert(['--no-daemon', 'args'])
 
+        then:
+        action instanceof WithLoggingAction
+        action.action instanceof RunBuildAction
+    }
+
+    def daemonOptionTakesPrecedenceOverSystemProperty() {
         when:
-        def action = factory.convert(['args'])
+        System.properties['org.gradle.daemon'] = 'false'
+        def action = factory.convert(['--daemon', 'args'])
 
         then:
-        1 * startParameterConverter.convert(!null) >> startParameter
+        action instanceof WithLoggingAction
+        action.action instanceof DaemonBuildAction
 
         when:
-        action.run()
+        System.properties['org.gradle.daemon'] = 'true'
+        action = factory.convert(['--no-daemon', 'args'])
 
         then:
-        1 * loggingManager.start()
-        1 * gradleLauncherFactory.newInstance(startParameter) >> gradleLauncher
-        1 * gradleLauncher.run() >> buildResult
-        1 * buildResult.failure >> failure
-        1 * buildCompleter.exit(failure)
+        action instanceof WithLoggingAction
+        action.action instanceof RunBuildAction
+    }
+    
+    def stopsDaemon() {
+        when:
+        def action = factory.convert(['--stop'])
+
+        then:
+        action instanceof WithLoggingAction
+        action.action instanceof StopDaemonAction
+    }
+
+    def runsDaemonInForeground() {
+        when:
+        def action = factory.convert(['--foreground'])
+
+        then:
+        action instanceof WithLoggingAction
+        action.action instanceof ActionAdapter
+        action.action.action instanceof DaemonMain
     }
 }
diff --git a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/DaemonBuildActionTest.groovy b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/DaemonBuildActionTest.groovy
new file mode 100644
index 0000000..87481e3
--- /dev/null
+++ b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/DaemonBuildActionTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher
+
+import org.gradle.initialization.ParsedCommandLine
+import org.gradle.launcher.protocol.Build
+import org.gradle.launcher.protocol.CommandComplete
+import org.gradle.logging.internal.OutputEventListener
+import org.gradle.messaging.remote.internal.Connection
+import spock.lang.Specification
+import org.gradle.initialization.BuildClientMetaData
+
+class DaemonBuildActionTest extends Specification {
+    final DaemonConnector connector = Mock()
+    final OutputEventListener listener = Mock()
+    final ExecutionListener completer = Mock()
+    final ParsedCommandLine commandLine = Mock()
+    final BuildClientMetaData clientMetaData = Mock()
+    final File currentDir = new File('current-dir')
+    final long startTime = 90
+    final DaemonBuildAction action = new DaemonBuildAction(listener, connector, commandLine, currentDir, clientMetaData, startTime)
+
+    def runsBuildUsingDaemon() {
+        Connection<Object> connection = Mock()
+
+        when:
+        action.execute(completer)
+
+        then:
+        1 * connector.connect() >> connection
+        1 * connection.dispatch({!null}) >> { args ->
+            Build build = args[0]
+            assert build.currentDir == currentDir
+            assert build.args == commandLine
+            assert build.clientMetaData == clientMetaData
+            assert build.startTime == startTime
+        }
+        1 * connection.receive() >> new CommandComplete(null)
+        1 * connection.stop()
+        0 * _._
+    }
+}
diff --git a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
index d3716a2..d1daceb 100644
--- a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
+++ b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
@@ -18,42 +18,40 @@ package org.gradle.launcher
 import spock.lang.Specification
 import org.junit.Rule
 import org.gradle.util.RedirectStdOutAndErr
+import org.gradle.api.Action
 
 class MainTest extends Specification {
     @Rule final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
-    final BuildCompleter completer = Mock()
+    final Main.BuildCompleter completer = Mock()
     final CommandLineActionFactory factory = Mock()
     final String[] args = ['arg']
     final Main main = new Main(args) {
         @Override
-        BuildCompleter createBuildCompleter() {
+        Main.BuildCompleter createBuildCompleter() {
             completer
         }
 
         @Override
-        CommandLineActionFactory createActionFactory(BuildCompleter buildCompleter) {
-            assert buildCompleter == completer
+        CommandLineActionFactory createActionFactory() {
             factory
         }
     }
 
     def createsAndExecutesCommandLineAction() {
-        Runnable action = Mock()
+        Action<ExecutionListener> action = Mock()
 
         when:
         main.execute()
 
         then:
         1 * factory.convert(args) >> action
-        1 * action.run()
-        1 * completer.exit(null)
-        0 * factory._
-        0 * action._
-        0 * completer._
+        1 * action.execute(completer)
+        1 * completer.exit()
+        0 * _._
     }
 
     def reportsActionExecutionFailure() {
-        Runnable action = Mock()
+        Action<ExecutionListener> action = Mock()
         RuntimeException failure = new RuntimeException('broken')
 
         when:
@@ -61,13 +59,12 @@ class MainTest extends Specification {
 
         then:
         1 * factory.convert(args) >> action
-        1 * action.run() >> { throw failure }
+        1 * action.execute(completer) >> { throw failure }
         outputs.stdErr.contains('internal error')
         outputs.stdErr.contains('java.lang.RuntimeException: broken')
-        1 * completer.exit(failure)
-        0 * factory._
-        0 * action._
-        0 * completer._
+        1 * completer.onFailure(failure)
+        1 * completer.exit()
+        0 * _._
     }
 
     def reportsActionCreationFailure() {
@@ -80,8 +77,8 @@ class MainTest extends Specification {
         1 * factory.convert(args) >> { throw failure }
         outputs.stdErr.contains('internal error')
         outputs.stdErr.contains('java.lang.RuntimeException: broken')
-        1 * completer.exit(failure)
-        0 * factory._
-        0 * completer._
+        1 * completer.onFailure(failure)
+        1 * completer.exit()
+        0 * _._
     }
 }
diff --git a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/RunBuildActionTest.groovy b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/RunBuildActionTest.groovy
new file mode 100644
index 0000000..f3bafee
--- /dev/null
+++ b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/RunBuildActionTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher
+
+import spock.lang.Specification
+import org.gradle.StartParameter
+import org.gradle.api.internal.project.ServiceRegistry
+import org.gradle.initialization.GradleLauncherFactory
+import org.gradle.GradleLauncher
+import org.gradle.BuildResult
+import org.gradle.initialization.BuildRequestMetaData
+
+class RunBuildActionTest extends Specification {
+    final StartParameter startParameter = new StartParameter()
+    final ServiceRegistry loggingServices = Mock()
+    final GradleLauncherFactory gradleLauncherFactory = Mock()
+    final ExecutionListener completer = Mock()
+    final GradleLauncher launcher = Mock()
+    final BuildResult result = Mock()
+    final BuildRequestMetaData requestMetaData = Mock()
+    final RunBuildAction action = new RunBuildAction(startParameter, loggingServices, requestMetaData) {
+        @Override
+        GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
+            return gradleLauncherFactory
+        }
+    }
+
+    def executesBuild() {
+        when:
+        action.execute(completer)
+
+        then:
+        1 * gradleLauncherFactory.newInstance(startParameter, requestMetaData) >> launcher
+        1 * launcher.run() >> result
+        _ * result.failure >> null
+        0 * _._
+    }
+
+    def executesFailedBuild() {
+        def failure = new RuntimeException()
+
+        when:
+        action.execute(completer)
+
+        then:
+        1 * gradleLauncherFactory.newInstance(startParameter, requestMetaData) >> launcher
+        1 * launcher.run() >> result
+        _ * result.failure >> failure
+        1 * completer.onFailure(failure)
+        0 * _._
+    }
+
+}
diff --git a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/StopDaemonActionTest.groovy b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/StopDaemonActionTest.groovy
new file mode 100644
index 0000000..91b21f8
--- /dev/null
+++ b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/StopDaemonActionTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.launcher
+
+import spock.lang.Specification
+import org.gradle.logging.internal.OutputEventListener
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.launcher.protocol.Stop
+import org.gradle.launcher.protocol.CommandComplete
+import org.gradle.initialization.BuildClientMetaData
+
+class StopDaemonActionTest extends Specification {
+    final DaemonConnector connector = Mock()
+    final OutputEventListener outputListener = Mock()
+    final ExecutionListener executionListener = Mock()
+    final BuildClientMetaData clientMetaData = Mock()
+    final StopDaemonAction action = new StopDaemonAction(connector, outputListener, clientMetaData)
+
+    def executesStopCommand() {
+        Connection<Object> connection = Mock()
+
+        when:
+        action.execute(executionListener)
+
+        then:
+        1 * connector.maybeConnect() >> connection
+        1 * connection.dispatch({it instanceof Stop})
+        1 * connection.receive() >> new CommandComplete(null)
+        1 * connection.stop()
+        0 * _._
+    }
+
+    def doesNothingWhenDaemonIsNotRunning() {
+        when:
+        action.execute(executionListener)
+
+        then:
+        1 * connector.maybeConnect() >> null
+        0 * _._
+    }
+
+    def reportsFailureToStop() {
+        Connection<Object> connection = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        action.execute(executionListener)
+
+        then:
+        1 * connector.maybeConnect() >> connection
+        1 * connection.dispatch({it instanceof Stop})
+        1 * connection.receive() >> new CommandComplete(failure)
+        1 * connection.stop()
+        1 * executionListener.onFailure(failure)
+        0 * _._
+    }
+}
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/openapi/CompatibilityIntegrationTest.groovy b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
similarity index 75%
rename from subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/openapi/CompatibilityIntegrationTest.groovy
rename to subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
index 9dfda94..d03625f 100644
--- a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/openapi/CompatibilityIntegrationTest.groovy
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
@@ -15,44 +15,52 @@
  */
 package org.gradle.integtests.openapi
 
+import org.gradle.api.internal.AbstractClassPathProvider
 import org.gradle.integtests.DistributionIntegrationTestRunner
+import org.gradle.integtests.fixtures.BasicGradleDistribution
 import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.PreviousGradleVersionExecuter
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.Jvm
+import org.junit.Assert
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.gradle.api.internal.AbstractClassPathProvider
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.junit.Assert
-import org.slf4j.LoggerFactory
 import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 
 @RunWith(DistributionIntegrationTestRunner.class)
-class CompatibilityIntegrationTest {
-    private final Logger logger = LoggerFactory.getLogger(CompatibilityIntegrationTest)
+class CrossVersionCompatibilityIntegrationTest {
+    private final Logger logger = LoggerFactory.getLogger(CrossVersionCompatibilityIntegrationTest)
     @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final TestResources resources = new TestResources()
 
-    private final PreviousGradleVersionExecuter gradle09rc1 = dist.previousVersion('0.9-rc-1')
+    private final BasicGradleDistribution gradle09rc1 = dist.previousVersion('0.9-rc-1')
+    private final BasicGradleDistribution gradle09rc2 = dist.previousVersion('0.9-rc-2')
 
     @Test
     public void canUseOpenApiFromCurrentVersionToBuildUsingAnOlderVersion() {
-        [gradle09rc1].each {
+        [gradle09rc1, gradle09rc2].each {
             checkCanBuildUsing(dist, it)
         }
     }
 
     @Test
     public void canUseOpenApiFromOlderVersionToBuildUsingCurrentVersion() {
-        [gradle09rc1].each {
+        [gradle09rc1, gradle09rc2].each {
             checkCanBuildUsing(it, dist)
         }
     }
 
     def checkCanBuildUsing(BasicGradleDistribution openApiVersion, BasicGradleDistribution buildVersion) {
+        if (!buildVersion.worksWith(Jvm.current())) {
+            System.out.println("skipping $buildVersion as it does not work with ${Jvm.current()}.")
+            return
+        }
+        if (!openApiVersion.worksWith(Jvm.current())) {
+            System.out.println("skipping $openApiVersion as it does not work with ${Jvm.current()}.")
+            return
+        }
+
         def testClasses = AbstractClassPathProvider.getClasspathForClass(CrossVersionBuilder.class)
         def junitJar = AbstractClassPathProvider.getClasspathForClass(Assert.class)
         def classpath = [testClasses, junitJar] + openApiVersion.gradleHomeDir.file('lib').listFiles().findAll { it.name =~ /gradle-open-api.*\.jar/ }
diff --git a/subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CompatibilityIntegrationTest/shared/build.gradle b/subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
similarity index 100%
rename from subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CompatibilityIntegrationTest/shared/build.gradle
rename to subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
diff --git a/subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CompatibilityIntegrationTest/shared/settings.gradle b/subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/settings.gradle
similarity index 100%
rename from subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CompatibilityIntegrationTest/shared/settings.gradle
rename to subprojects/gradle-open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/settings.gradle
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
index 161fec4..8358718 100644
--- a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
@@ -19,6 +19,7 @@ import aQute.lib.osgi.Analyzer;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.Factory;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.java.archives.Attributes;
 import org.gradle.api.java.archives.internal.DefaultManifest;
 import org.gradle.api.plugins.osgi.OsgiManifest;
 import org.gradle.util.GUtil;
@@ -65,6 +66,10 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
             for (Map.Entry<Object, Object> entry : osgiManifest.getMainAttributes().entrySet()) {
                 effectiveManifest.attributes(WrapUtil.toMap(entry.getKey().toString(), (String) entry.getValue()));
             }
+            effectiveManifest.attributes(this.getAttributes());
+            for(Map.Entry<String, Attributes> ent : getSections().entrySet()) {
+                effectiveManifest.attributes(ent.getValue(), ent.getKey());
+            }
         } catch (Exception e) {
             throw UncheckedException.asUncheckedException(e);
         }
diff --git a/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
index 8cc36ed..a0c5d02 100644
--- a/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
+++ b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
@@ -19,6 +19,8 @@ import aQute.lib.osgi.Analyzer;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.Factory;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.java.archives.Attributes;
+import org.gradle.api.java.archives.internal.DefaultAttributes;
 import org.gradle.api.java.archives.internal.DefaultManifest;
 import org.gradle.util.GUtil;
 import org.gradle.util.WrapUtil;
@@ -33,6 +35,7 @@ import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Map;
 import java.util.jar.Manifest;
 
 import static org.junit.Assert.*;
@@ -42,6 +45,10 @@ import static org.junit.Assert.*;
  */
 @RunWith(JMock.class)
 public class DefaultOsgiManifestTest {
+    private static final String ARBITRARY_SECTION = "A-Different-Section";
+    private static final String ARBITRARY_ATTRIBUTE = "Silly-Attribute";
+    private static final String ANOTHER_ARBITRARY_ATTRIBUTE = "Serious-Attribute";
+
     private DefaultOsgiManifest osgiManifest;
     private Factory<ContainedVersionAnalyzer> analyzerFactoryMock;
     private ContainedVersionAnalyzer analyzerMock;
@@ -129,7 +136,12 @@ public class DefaultOsgiManifestTest {
         prepareMock();
 
         DefaultManifest manifest = osgiManifest.getEffectiveManifest();
-        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(getDefaultManifestWithOsgiValues().getAttributes());
+        DefaultManifest defaultManifest = getDefaultManifestWithOsgiValues();
+        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(defaultManifest.getAttributes());
+        for(Map.Entry<String, Attributes> ent : defaultManifest.getSections().entrySet()) {
+            expectedManifest.attributes(ent.getValue(), ent.getKey());
+        }
+
         assertThat(manifest.getAttributes(), Matchers.equalTo(expectedManifest.getAttributes()));
         assertThat(manifest.getSections(), Matchers.equalTo(expectedManifest.getSections()));
     }
@@ -142,8 +154,11 @@ public class DefaultOsgiManifestTest {
         otherManifest.mainAttributes(WrapUtil.toMap("somekey", "somevalue"));
         otherManifest.mainAttributes(WrapUtil.toMap(Analyzer.BUNDLE_VENDOR, "mergeVendor"));
         osgiManifest.from(otherManifest);
-        DefaultManifest expectedManifest = new DefaultManifest(fileResolver);
-        expectedManifest.attributes(getDefaultManifestWithOsgiValues().getAttributes());
+        DefaultManifest defaultManifest = getDefaultManifestWithOsgiValues();
+        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(defaultManifest.getAttributes());
+        for(Map.Entry<String, Attributes> ent : defaultManifest.getSections().entrySet()) {
+            expectedManifest.attributes(ent.getValue(), ent.getKey());
+        }
         expectedManifest.attributes(otherManifest.getAttributes());
 
         DefaultManifest manifest = osgiManifest.getEffectiveManifest();
@@ -175,6 +190,7 @@ public class DefaultOsgiManifestTest {
         osgiManifest.instruction(Analyzer.IMPORT_PACKAGE, new String[]{"pack3", "pack4"});
         osgiManifest.setClasspath(fileCollection);
         osgiManifest.setClassesDir(new File("someDir"));
+        addPlainAttributesAndSections(osgiManifest);
     }
 
     private void prepareMock() throws Exception {
@@ -220,6 +236,14 @@ public class DefaultOsgiManifestTest {
         manifest.getAttributes().put(Analyzer.BUNDLE_DOCURL, osgiManifest.getDocURL());
         manifest.getAttributes().put(Analyzer.EXPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE), ","));
         manifest.getAttributes().put(Analyzer.IMPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE), ","));
+        addPlainAttributesAndSections(manifest);
         return manifest;
     }
+
+    private void addPlainAttributesAndSections(DefaultManifest manifest) {
+        manifest.getAttributes().put(ARBITRARY_ATTRIBUTE, "I like green eggs and ham.");
+        Attributes sectionAtts = new DefaultAttributes();
+        sectionAtts.put(ANOTHER_ARBITRARY_ATTRIBUTE, "Death is the great equalizer.");
+        manifest.getSections().put(ARBITRARY_SECTION, sectionAtts);
+    }
 }
diff --git a/wrapper/gradle-wrapper.properties b/wrapper/gradle-wrapper.properties
index 6c0b354..285ab11 100644
--- a/wrapper/gradle-wrapper.properties
+++ b/wrapper/gradle-wrapper.properties
@@ -18,7 +18,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
-distributionVersion=0.9-rc-1
+distributionVersion=0.9-rc-2
 zipStorePath=wrapper/dists
 urlRoot=http\://dist.codehaus.org/gradle
 distributionName=gradle

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/gradle-1.12.git



More information about the pkg-java-commits mailing list